Go Down

Topic: What does the F() do exactly? (Read 7 times) previous topic - next topic

liudr

I am trying to understand exactly what F() does when its used in something like Serial.print(F("Hello from PROGMEM"));

Here is all I could find in WString.h

class __FlashStringHelper;
#define F(string_literal) (reinterpret_cast<__FlashStringHelper *>(PSTR(string_literal)))

Found this in pgmspace.h

# define PSTR(s) ((const PROGMEM char *)(s))

There is no other things besides a simple class name and this macro.

Can someone explain to me the syntax in the F() macro please? My OOP is not strong enough to understand this line :~

BTW, realized the lib author was using doxgen :) I can use the code to learn how to use doxgen :)

robtillaart


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.


Rob Tillaart

Nederlandse sectie - http://arduino.cc/forum/index.php/board,77.0.html -
(Please do not PM for private consultancy)

Coding Badly

Quote
Found this in pgmspace.h
# define PSTR(s) ((const PROGMEM char *)(s))


That is the definition used in the documentation.  The definition used when compiling is a few lines below...

Code: [Select]
# define PSTR(s) (__extension__({static char __c[] PROGMEM = (s); &__c[0];}))

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".

From Print.h...

Code: [Select]
class Print
{
...
    size_t print(const __FlashStringHelper *);
...
    size_t print(const char[]);


Code: [Select]
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.

mkwired

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 [FLASH].  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.

This does not work.
Code: [Select]

Serial.print(PSTR("Hello, World"));


This works.
Code: [Select]

Serial.print(F("Hello, World"));

johnwasser

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.
Send Bitcoin tips to: 1L3CTDoTgrXNA5WyF77uWqt4gUdye9mezN
Send Litecoin tips to : LVtpaq6JgJAZwvnVq3ftVeHafWkcpmuR1e

ninja2

Coould this F() syntax be used in place of rather cumbersome:

    strcpy_P(buffer, (char*)pgm_read_word(&(str_table[index])));

Could any of these work for example

    Serial.print(F(str_table(2)));
    or
    Serial.print(F(str_2));

(where
    str_2 prog_char str_2[]  PROGMEM = "Hello World";)

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...

johnwasser


Coould this F() syntax be used in place of rather cumbersome:

    strcpy_P(buffer, (char*)pgm_read_word(&(str_table[index])));


I don't think so because the argument to F() has to be a string constant.
What I think you CAN do is:

   Serial.print(reinterpret_cast<__FlashStringHelper *>(str_table[index]));

Passing  __FlashStringHelper * to .print or .println tells it that the argument is a pointer to a character string in FLASH.
Send Bitcoin tips to: 1L3CTDoTgrXNA5WyF77uWqt4gUdye9mezN
Send Litecoin tips to : LVtpaq6JgJAZwvnVq3ftVeHafWkcpmuR1e

Coding Badly


Coould this F() syntax be used in place of rather cumbersome:

    strcpy_P(buffer, (char*)pgm_read_word(&(str_table[index])));


This may help...
http://arduiniana.org/libraries/flash/

liudr


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 ( :smiley-sweat: ;)) 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!

ninja2

#10
Feb 11, 2012, 11:52 am Last Edit: Feb 11, 2012, 11:55 am by ninja2 Reason: 1
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):

Serial.print(reinterpret_cast<__FlashStringHelper *>(str_table[index]));


does C need, or interpret variables with leading underscores, like ___THIS, or is this just a software weenie posey naming convention ?

mmcp42

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?!? :)
there are only 10 types of people
them that understands binary
and them that doesn't

ninja2

ahah, that makes sense.

although ... the technique seems to be used by lots of C coders in lots of programs, which theoretically undermines their objective  for using it :.

Udo Klein

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.
Check out my experiments http://blog.blinkenlight.net

mmcp42


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

Code: [Select]

void setup(void)
{
  Serial.begin(19200);
}
void loop(void)
{
  Serial.println(F("abcdefg"));
  Serial.println(F("abcdefg"));
}

and
Code: [Select]

void setup(void)
{
  Serial.begin(19200);
}
void loop(void)
{
  Serial.println(F("abcdefg"));
  Serial.println(F("xbcdefg"));
}

both compiled to 2040 bytes
so it looks like no optimisation
there are only 10 types of people
them that understands binary
and them that doesn't

Go Up