declare string variable

I have been declaring string variables with String foo; I have been told on this forum the String object (with capital S) can "Break the Arduino".

So how does one declare a string variable? With "char foo;"?

char foo[] = {"Hello World"};

void setup()
{
  Serial.begin(115200);
  while (!Serial);
  Serial.println(foo);
}

void loop()
{
}

Or if you just need a read-only string:

const char *foo = "Hello World";

The string itself is saved somewhere in the static memory of your program. You save a pointer that points to the first character of the string in the variable "foo". You're not allowed to write to string literals, so the pointer is const (i.e. read-only).

Pieter

You're not allowed to write to string literals

But you can change them

void setup()
{
  char foo[] = {"Hello World"};
  Serial.begin(115200);
  while (!Serial);
  Serial.println(foo);
  foo[0] = 'X';
  Serial.println(foo);
  strcpy(foo, "Wibble");
  Serial.println(foo);
}

UKHeliBob:
But you can change them

void setup()

{
  char foo[] = {"Hello World"};
  Serial.begin(115200);
  while (!Serial);
  Serial.println(foo);
  foo[0] = 'X';
  Serial.println(foo);
  strcpy(foo, "Wibble");
  Serial.println(foo);
}

'foo' is not a literal. It's an array of buffer memory. The literal string is "Hello World". You're not allowed to write to the memory locations storing it.

PieterP:
Or if you just need a read-only string:

const char *foo = "Hello World";

I'd make it idiot proof so user code can't change the pointer thus stranding the literal in memory forever:

const char * const foo = "Hello World";

or

const foo[] = "Hello World";

'foo' is not a literal. It's an array of buffer memory. The literal string is "Hello World". You're not allowed to write to the memory locations storing it.

What does

foo[0] = 'X';

do if it is not write to the location of the first character of the string ?

1 Like

UKHeliBob:
But you can change them

void setup()

{
  char foo[] = {"Hello World"};
  Serial.begin(115200);
  while (!Serial);
  Serial.println(foo);
  foo[0] = 'X';
  Serial.println(foo);
  strcpy(foo, "Wibble");
  Serial.println(foo);
}

You're not changing the literal. You create a char array with the same length as the string literal, and then you copy the literal to this buffer. This means that using "char []" instead of "const char *" uses twice as much RAM.

char foo[] = "foo" is not the same as char *foo = "foo".

Have a look at the disassembly:

You can see the sbiw r28,12 on line 12 (subtract 12 from stack pointer) that allocates space on the stack for the copy of the string. (12 = strlen("Hello World") + 1 for the terminating null.)
Then it copies the literal "Hello World" from line 6 into the buffer using the loop on lines 18-27. (Load the address of the literal, load 12 in the counter, copy a character, decrement counter, repeat until it hits zero.)[/tt]

gfvalvo:
I'd make it idiot proof so user code can't change the pointer thus stranding the literal in memory forever:

const char * const foo = "Hello World";

Good point, but might be overkill and confusing for many beginners. In that case, I would prefer

const auto foo = "Hello World";

The type of foo will be "const char * const".

You're not changing the literal. You create a char array with the same length as the string literal, and then you copy the literal to this buffer. This means that using "char []" instead of "const char *" uses twice as much RAM.

Thanks for the explanation. I must have known that at some time

And while we're on the topic, you can also save the string in program memory to save RAM.

The default PSTR macro (program memory string) uses exotic compiler extensions, so you can't use them outside of functions, but luckily, you can circumvent this using a lambda expression:

[color=#5e6d03]#undef[/color] [color=#000000]PSTR[/color]
[color=#5e6d03]#define[/color] [color=#000000]PSTR[/color][color=#000000]([/color][color=#000000]s[/color][color=#000000])[/color] [color=#000000]([/color][color=#000000][[/color][color=#000000]][/color][color=#000000]{[/color] [color=#00979c]static[/color] [color=#00979c]const[/color] [color=#00979c]char[/color] [color=#000000]c[/color][color=#000000][[/color][color=#000000]][/color] [color=#00979c]PROGMEM[/color] [color=#434f54]=[/color] [color=#000000]([/color][color=#000000]s[/color][color=#000000])[/color][color=#000000];[/color] [color=#5e6d03]return[/color] [color=#434f54]&[/color][color=#000000]c[/color][color=#000000][[/color][color=#000000]0[/color][color=#000000]][/color][color=#000000];[/color] [color=#000000]}[/color][color=#000000]([/color][color=#000000])[/color][color=#000000])[/color]

[color=#00979c]const[/color] [color=#00979c]auto[/color] [color=#000000]foo[/color] [color=#434f54]=[/color] [color=#000000]F[/color][color=#000000]([/color][color=#005c5f]"I am a string in PROGMEM"[/color][color=#000000])[/color][color=#000000];[/color]
[color=#00979c]const[/color] [color=#00979c]auto[/color] [color=#000000]bar[/color] [color=#434f54]=[/color] [color=#005c5f]"I am a string in RAM"[/color][color=#000000];[/color]

The type of "foo" will be "const __FlashStringHelper * const" in this case.

Nice. What is the final '()' pair right before the closing ')'?

#define PSTR(s) ([]{ static const char c[] PROGMEM = (s); return &c[0]; }())

It calls the lambda, so it returns the address of the progmem string. The return value will then be used to initialize "foo".
It's an Immediately Invoked Function Expression (IIFE).

Well, that won't be "confusing for many beginners" :smiley:
But, I've always been frustrated by not being able to create "F()" macro string pointers outside of a function. So, thanks.

gfvalvo:
Well, that won't be "confusing for many beginners" :smiley:
But, I've always been frustrated by not being able to create "F()" macro string pointers outside of a function. So, thanks.

Yeah, it's not the prettiest solution, but it's the best I've found so far.

In my defense, the original PSTR macro isn't much better :slight_smile:

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