reinterpret_cast is a cast that can convert any type into any pointer type.
In this case it takes care that the PSTR() "pstring" is converted to a pointer to __FlashStringHelper * class.
Most important in practice is that this construction prevents the compiler from throwing all kinds of warnings/errors.
PSTR works somewhat like a function. Storage (an array of characters named __c) for the string constant is created in Flash (PROGMEM tells the compiler and linker to place __c in Flash). The address of the storage (of __c) is "returned".
class __FlashStringHelper;
#define F(string_literal) (reinterpret_cast<__FlashStringHelper *>(PSTR(string_literal)))
PSTR returns a char*. If we do nothing special, print(const char[]) would be called which is not what we what. print(const char[]) does not know how to print strings stored in Flash.
By type-casting, we force the compiler to use print(const __FlashStringHelper *) which does know how to print strings stored in Flash.
Reading data (ex. a string) from FLASH, requires the use of some functions. The AVR is a Harvard architecture. Code and Data are stored separately. The following statement will compile but will not print the string. The reason is because the member function print is passed an argument of type char* which is the base address of the string that's stored in FLASH. The problem is that de-referencing a char* returns the char stored in the Data space (RAM) and not from the Code space . The F() macro changes the type from char* to __FlashStringHelper*. Now, that the argument is a different type, one can create functions that accept that type and called the correct functions to retrieve the data from Code space. The member functions print and println from the class Serial have these overloads.
The class __FlashStringHelper has no body, just a type.
The "reinterpret_cast" tells the compiler that you know that the value being cast is not compatible with the destination type. Unlike a regular cast, no conversion is done. The ONLY safe operation is to cast the value BACK to what it was before, which is what the print function does.
mromani:
This thread is the best explanation of the F() macro I found so far... Should be made sticky or turned into some official doc page...
I second that! Thanks so much everyone! So it comes down to:
ATMEGA328P uses Harvard architecture thus separates data and code memory space into SRAM and PROGMEM (AKA FLASH). Accessing data in data memory requires nothing special. Accessing data in code memory requires special functions such as pgm_read_byte and pgm_read_word. String pointers to address in SRAM (data memory) are proper for print but string pointers to PROGMEM (code memory) are not proper for print because the pointer points to code memory. A special print version that handles code memory pointers with pgm_read_byte or pgm_read_word needs to be called. So the F() tells the compiler, through a complex but understandable ( ;)) way, that the string is to be stored only in PROGMEM (code memory) to save SRAM (data memory) and the special print that handles code space pointer is to be called.
I'll continue to work on this explanation for a typical arduino user to understand. I for one, have finally understood it (minus how to tell compiler to only store a string in PROGMEM and whether the compiler optimizes duplicates) thanks to the dozen replies!
as one who's been a light weight dabbler in code over the years, but never C or C++ until recently, I am constantly amazed at the depth of the C language, how compact yet powerful it is, and how much I still have to learn when I see a command like this (which I understand what it does in principle, but not all the components):
I believe it's just a technique to avoid using "words" that people might already have in their program
who in their right mind would use variables like __fred?!?
If you want to learn if the compiler optimizes duplicates you could just create a sketch that contains duplicates. Then use avr-objdump in order to look into the .elf file and see if the output contains the duplicates.
or create a sketch with duplicates
then make a one character change
if the sketch stays the same size you had duplicates
if it increases by the length of the string - then it was being optimised
Thanks for finding out the answer mmcp42. I guess F() is a part of the solution to SRAM problem but eventually becomes part of the FLASH problem. Does anyone know how to turn on optimization option for the compiler while using arduino ide?
I believe it's just a technique to avoid using "words" that people might already have in their program
who in their right mind would use variables like __fred?!?
Right. One would have to count (underscores) instead of read (words)