0

I found this code sample from the FSBrowser (Flash File System) example in the ESP8266WebServer library:

replyServerError(FPSTR(FS_INIT_ERROR));
replyBadRequest(F("DIR ARG MISSING"));

I was a bit confused at what F() and FPSTR() do, so I looked up the source (WString.h) and found this:

#define FPSTR(pstr_pointer) (reinterpret_cast<const __FlashStringHelper *>(pstr_pointer))
#define F(string_literal) (FPSTR(PSTR(string_literal)))

Which raised more questions. How does this code work and what does this added complexity gain over just using char* or String as is?

EDIT: Added Metrics

Here's the memory usage before and after I removed all 25 instances of F() from a file system class, measured by PlatformIO Project Inspector:

With F() Without F() Difference
RAM Used 29,488 bytes (36.0%) 29,792 bytes (36.4%) +304 bytes
Flash Used 355,312 bytes (34%) 355,188 bytes (34.0%) -124 bytes
Total (uncompressed) 359,472 bytes 359,344 bytes -128 bytes

Adding F() decreases RAM usage by 1% and increases Flash by around half that. That's for a single class with 25 instances of F(). For a program containing 20x this code, the difference can be significant. However, you'll run out of RAM before you get there. The space savings will be limited to around 5-8%. Still not nothing.

6
  • 1
    On the ESP32 (as well as most 32-bit boards), the macros are nothing but a cast, on AVR-based boards (i.e. Arduino Uno), special low-level functions are required to read data from flash. The macros are hiding this difference from the user, so that the code can be written such that it compiles and runs on both platforms. – PMF Jan 17 at 13:38
  • @PMF thanks for clarifying the use case. Does that mean I don't need to use F() or FPSTR() in my own code if I only plan on using the ESP8266? – DV82XL Jan 17 at 14:08
  • 1
    I think not, but it may still depend on the compiler (it may still do a copy from Flash to Ram on bootup). You can find out by adding/removing a few F()'s and check the resulting RAM usage (last line of the compiler output). If it doesn't change, you don't need it. – PMF Jan 17 at 15:16
  • 1
    On the ESP8266 for sure, F() macros can save heap space. – dandavis Jan 20 at 3:41
  • @dandavis Thanks! I added an edit with some metrics. The code author sometimes uses F(), sometimes FPSTR(), and sometimes neither. What is a good criteria to decide when to use each approach? – DV82XL Jan 20 at 4:35
2

The purpose of the PSTR() macro is to configure the string to be used directly from flash memory. The string is then not loaded to dynamic memory at runtime as it would be without PROGMEM specifier set by the macro.

The purpose of the FPSTR() macro is to cast a string to 'dummy' FlashStringHelper type to help compiler choose the right overloaded function if dynamic memory and flash memory version of a function is available. An example would be the print function.

The F() macro combines this two macros. It makes the string a PROGMEM string and casts it to FlashStringHelper.

2
  • Thanks for the reply! How does FlashStringHelper help function overloading? Does it cast itself to either a dynamic string or flash string depending on its input parameter? – DV82XL Jan 17 at 14:14
  • 1
    @DV82XL, PROGMEM string is const char* too. in C there are functions like sprintf_P for PROGMEM strings , but Arduino decided to use a special 'dummy' type for the PROGMEM string and uses print(const char*) for normal string and print(flashstringhelper*) for PROGMEM string. there is no Serial.print_P in standard Arduino API (some cores have it). to use the right function the C++ compiler evaluates the parameters of functions with same name github.com/arduino/ArduinoCore-avr/blob/… – Juraj Jan 17 at 16:27

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.