Call to sprintf() makes my local char[] global ?

I am trying to reduce the footprint of my globals in my project and while messing around found 2 very strange behaviors. Yes this program is contrived, it’s for illustrative purposes, and I wanted to make certain that the compiler wasn’t optimizing away the msg variable.

  1. the msg variable below is obviously local, but when I include the call to sprintf() it apparently is moved to the global space:
void setup() {
  pinMode(1, OUTPUT);
  pinMode(2, INPUT);
}

void loop() {
  digitalWrite(1, f1());
}

bool f1() {
  char msg[100] = "1234";

  if (digitalRead(1))
    msg[0] = '1';
  else
    msg[0] = '0';

  // when included: Global variables use 117 bytes (5%) of dynamic memory
  // when excluded: Global variables use 13 bytes  (0%) of dynamic memory
  sprintf(msg, "%d", 1);

  int i;
  if (msg[0]) {
    return true;
  } else {
    return false;
  }
}

2.The second weird thing I noticed is when msg is initialized to a length of <= 3 chars, it’s local, but when initialized to > 3 chars, it apparently is moved to the global space:

void setup() {
  pinMode(1, OUTPUT);
  pinMode(2, INPUT);
}

void loop() {
  digitalWrite(1, f1());
}

bool f1() {
  // Global variables use 17  bytes (0%) of dynamic memory
//  char msg[100] = "123";

  // Global variables use 117 bytes (5%) of dynamic memory
  char msg[100] = "1234";

  if (digitalRead(1))
    msg[0] = '1';
  else
    msg[0] = '0';

  sprintf(msg, "%d", 1);

  int i;
  if (msg[0]) {
    return true;
  } else {
    return false;
  }
}

Ok, it looks like sprintf() is allocating room in the global space for internal use as well as string constants passed to it. If I could move those constants to the program space, that would be great, but the F() macro doesn't work ...

void setup() {}

void loop() {
  f1();
}

bool f1() {
  char msg[100] = "1234";

  sprintf(msg, "test A %d", 1); // Including only this line: Global variables use 119 bytes (5%) of dynamic memory
  sprintf(msg, "test A %d", 1); // Including this and above: no change
  sprintf(msg, "test B %d", 1); // Including this and above: Global variables use 129 bytes (6%) of dynamic memory
}

What problem are you trying to solve?

This won't work as you expect. "1" is a C-string, not a character.

    msg[0] = "1";

This problem is not specific to sprintf. If you don't use sprintf, the compiler optimizes out the char array.

If you look at the compiler output, you'll see that the compiler allocates 100 bytes in static memory, and 100 bytes on the stack. Then it copies the 5 first characters from the static string to the local array, and finally, it fills the rest of the local array with zeros. The 95 zero bytes in static memory are never used.

Different compilers seem to behave completely differently, AVR GCC 4.5.4 doesn't include the 95 zero bytes, for example.
It could be a bug in GCC, might be worth opening a ticket. If you do so, please post the link here.

Pieter

jremington:
This won't work as you expect.

Crud, that was a typo, thank you. Fixed in my examples.

gfvalvo:
What problem are you trying to solve?

Big picture I am trying to free up SRAM. I just learned about PSTR() and the _P methods such as strcat_P so I'm probably going to play with that.

But for the purposes of this post, I'm hoping to learn about how sprintf() works and why it's behaving this way...

PieterP:
Compiler Explorer

That is awesome, thank you! I will definitely look at this more. Any insight into what the compiler is doing helps...