I have a class which has a const char *
property:
class A
{
public:
const PROGMEM char* text;
};
void setup()
{
// A a{"Hello World!"};
// A a{PSTR("Hello World!")};
A a;
a.text = PSTR("Hello World!");
Serial.begin(9600);
delay(30);
Serial.println(a.text);
}
void loop()
{
}
I’d like to spare some RAM using PROGMEM
.
How to initialize a
object then?
Obviously, it can’t be like this:
A a{PSTR("Hello World!")};
This will do:
A a;
a.text = PSTR("Hello World!");
However, I need to pass string to the constructor.
2 Answers 2
It is a shame that gcc only supports the __flash
qualifier in C mode,
not in C++, so we have to use PROGMEM
instead. Unlike __flash
, which
qualifies a variable just like const
, the PROGMEM
attribute only has
effect when allocating room for a variable. Once the allocation is done,
the compiler forgets about the attribute. In particular, a declaration
such as
const PROGMEM char* text;
does not allocate flash space, so it generates the warning
warning: ‘__progmem__’ attribute ignored [-Wattributes]
const PROGMEM char* text;
^
You can thus forget the attribute when declaring a pointer, as there is
no such thing as a "pointer to PROGMEM
". You just use a const char *
instead.
Now, the second issue is that, as far as Serial.println()
is
concerned, the pointer above is a plain const char *
, so it will
interpret it as an address in RAM, and print garbage. If you want
Serial.println()
to know you are giving it an address in flash, you
should provide it with a const __FlashStringHelper*
pointer.
Here is the solution I propose. Tested on an Uno-compatible board:
class A
{
public:
A(const char* s)
: text(reinterpret_cast<const __FlashStringHelper *>(s)) {}
const __FlashStringHelper* text;
};
void setup()
{
A a{PSTR("Hello World!")};
Serial.begin(9600);
Serial.println(a.text);
}
void loop(){}
Edit: After seeing the last version of Juraj’s answer, I must say
that I agree with him. Since we are using the Arduino API, it makes more
sense for the constructor to take a const __FlashStringHelper*
, and
for the caller to use the F()
macro.
const char* text;
is a pointer to constant not a constant pointer (char * const text
is a constant pointer). So you can assign a pointer to a constant char array to const char* text;
even a pointer to an array in PROGMEM.
The compiler doesn't know the difference between a PROGMEM pointer and a pointer in SRAM. It is on you to work in code with a pointer to PROGMEM the right way.
so remove PROGMEM from const PROGMEM char* text;
add constructor to initialize an object.
A(const char* _text) {
text = _text;
}
and then
A a(PSTR("Hello World!"));
EDIT:
you could use Arduino's F() macro and __FlashStringHelper type, because it is supported by Serial print and co.
class A
{
public:
A(const __FlashStringHelper* _text) {
text = _text;
}
const __FlashStringHelper* text;
};
void setup()
{
A a(F("Hello World!"));
Serial.begin(9600);
delay(30);
Serial.println(a.text);
}
void loop()
{
}
__FlashStringHelper type is trick to distinguish PROGMEM strings from char arrays in SRAM..
-
This is not an answer on my question. How to store strings in flash memory? Strings must be passed to the contructor.zhekaus– zhekaus2020年02月22日 16:48:04 +00:00Commented Feb 22, 2020 at 16:48
-
1it is unclear what is what you don't know. I enhanced the answer2020年02月22日 17:25:55 +00:00Commented Feb 22, 2020 at 17:25
-
Hum... Your solution won't let use Serial.println. And added user-defined constructor does not any difference here.zhekaus– zhekaus2020年02月22日 22:03:19 +00:00Commented Feb 22, 2020 at 22:03
-
@zhekaus, yes, your question combined 3 problems. Edgar got them all, but the true solution is to use F macro, not PSTR, if possible. I enhanced the answer2020年02月23日 06:18:24 +00:00Commented Feb 23, 2020 at 6:18
a.text = F("Hello World!");
do what you want?const __FlashStringHelper*
- it'sconst char*