Why unused local variable take SRAM?

Hello gang,
I've throught the internet before putting my question but I'm human and sorry if I've the subject where there is a similar question.

So in my program, I got a function which is called only with a combination of buttons.

void serialRequest(int subMenuSelec){
  char stringToSend[50];
  uint32_t checksum;
  switch (subMenuSelec)
  {
  case 1:
    sprintf(stringToSend,"%s",(const char *)pgm_read_word(&serialRquestStr[0]));
    Serial.println(stringToSend);
    Serial.flush();
    break;

  case 2:
    sprintf(stringToSend,"%s",(const char *)pgm_read_word(&serialRquestStr[1]));
    
    Serial.println(stringToSend);
    Serial.flush();
    break;
  
  default:
    break;
  }

}

My understanding is for exemple the variable "char stringToSend[50]" will take some of the SRAM space only when the function "serialRequest" is called.

But when I use the function freeMemory()); in the loop, I can see a reducing of the Freememory when I increase the size of the local variable "char stringToSend" even thought I never do the actions on the buttons to call the function "serialRequest".

my code is quite big (800 lines) so I'm trying to get the clearest possible on my issue so you can help me, don't hesitate to for moreinfo if you need.

Thanks for the answers !
cheers

The compiler can do pretty nifty optimizations, but this is weird.

When a variable is declared on the stack, then it will be released when the function is finished.
That means you have to call the freeMemory() function from inside the serialRequest() function to see the memory usage of 'stringToSend' on the stack.

Perhaps the compiler decides that it is too big for the stack and allocates it from global memory space. I have never heard of that, so that is very unlikely.
Perhaps something else is going on. I really have no clue :frowning:

When you can make a simple and small sketch that shows the problem, then there are a number of people on this forum very interested. I'm afraid that when you shorten the sketch, the problem also disappears.
You can show your sketch, maybe there is something wrong with pointers or arrays that could cause it. A sketch is not big when it is below 6000 lines :wink:

When you want to have fun with memory, then you could try my HighWaterMark.ino.
I have it running in one project with an Arduino Uno, and it provides a good indication about the memory usage.

yes the compiler does this. making arrays global

So I've made a shorter code

#include <MemoryFree.h>

const char* const serialRquestStr[] PROGMEM = {"REC_START","REC_STOP"};

void setup() {
  Serial.begin(9600);
  pinMode(2,INPUT_PULLUP);
  
}

void loop() {
  int valBut=digitalRead(2);
  if(valBut==0){
    testFunc();
  }
  else{
    Serial.println(freeMemory());
  }
  
}

void testFunc(void){
  char stringToSend[200]="totototototototto";
  Serial.print(stringToSend);
Serial.println(freeMemory());
  //strcat(stringToSend,(const char *)pgm_read_word(&serialRquestStr[0]));
  //sprintf(stringToSend,"%s",(const char *)pgm_read_word(&serialRquestStr[0]));
}

I've noticed that if I use Serial.print, or sprintf, or strcat inside the function, the freememory in the loop will change regarding the size de stringToSend.
If i don't press button, the function is never called, i get freememory of 1448.
If I press button I got free memory inside function of 1648.

I'm lost :frowning:

PS: I use platformio for the full code, this small code was in Arduino IDE

Which MemoryFree library is that ? I can't find it in the Library Manager and there are a few on Github.

I took it from this repository

Confirmed as Juraj wrote, the compiler makes it a global variable.

Using Arduino IDE 1.8.13
Tested with Arduino Uno

Compiling:
Global variables use 392 bytes (19%) of dynamic memory
The 'stringToSend' is part of that, I can make the 'stringToSend' smaller and larger and the dynamic memory usage changes accordingly.

Running:
Serial monitor says 1446 bytes, with pin 2 to GND and without. Always 1446.

The initialization causes a certain compiler behaviour.
This results in something else:

  //  char stringToSend[200]="totototototototto";
  char stringToSend[200];
  strcpy( stringToSend, "totototototototto");

Then the dynamic memory is 210 (the text "tototot..." uses dynamic memory), and the 'stringToSend' is no longer part of it.
However, it is still not on the stack in the function, because the freeMemory does not change when entering that function. So the compiler still puts it somewhere else and keeps it there and does not release that memory.

@Juraj, can you enlighten us ?

Seems related to this subject
https://forum.arduino.cc/index.php?topic=658558.0

But I'm not sure, even less sure about the solution they show :confused:

Edit: I've tried quick with sprintf_P function, not sure I've understood how to used it, but seems having the same behavior about SRAM

I would say that at this point, you need to look at the assembly code this is producing.

"Obviously", if you initialize a local array variable, the code has to contain the values that you're initializing it to as well.

Also, the compiler and linker will aggressively optimize and re-organize your code. If you call a function only once, it is likely to be inlined into the next higher level, and the storage it uses "promoted" to the beginning of THAT function...

I think the problem is that the initializer for a 200-byte array is 200 bytes long. If you say:

void testFunc(void) {
  char stringToSend[200]="to";
}

You get a 200-byte initializer with 't', 'o', and 198 nulls. That sits in PROGMEM, waiting for the time it is copied to the stack to initialize the variable.

You get a 200-byte initializer with 't', 'o', and 198 nulls. That sits in PROGMEM, waiting for the time it is copied to the stack to initialize the variable.

Looks to me like it puts the initializer in RAM...

#include <avr/io.h>
void foox(char *) __attribute__((weak));
void testFunc(void) {
  char stringToSend[200]="1 2 3 4 to";
  foox(stringToSend);
}
int main() {
    testFunc();
}

Here's the resulting code:

00800100 <__data_start>:   [color=red];;; Initialized data - only "enough"[/color]
  800100: 31 20       and r3, r1
  800102: 32 20       and r3, r2
  800104: 33 20       and r3, r3
  800106: 34 20       and r3, r4
  800108: 74 6f       ori r23, 0xF4 ; 244
 ...

Disassembly of section .text:
  // vectors and stuff.
  
00000096 <testFunc>:
  96: cf 93       push r28
  98: df 93       push r29

  9a: cd b7       in r28, 0x3d [color=red]; get Stack ptr.[/color]
  9c: de b7       in r29, 0x3e
  9e: c8 5c       subi r28, 0xC8 [color=red]; 200 - Make space on Stack[/color]
  a0: d1 09       sbc r29, r1         [color=red]; R28/29 is stack frame ("Y")[/color]
  a2: 0f b6       in r0, 0x3f
  a4: f8 94       cli
  a6: de bf       out 0x3e, r29 ; save modified stack ptr.
  a8: 0f be       out 0x3f, r0 ; 
  aa: cd bf       out 0x3d, r28 ; 
  ac: 8b e0       ldi r24, 0x0B [color=red]; size of initialized data[/color]
  ae: e0 e0       ldi r30, 0x00 [color=red]; pointer to init data[/color]
  b0: f1 e0       ldi r31, 0x01 ;  (in "Z")
  b2: de 01       movw r26, r28        ; addr of stack data in "X"
  b4: 11 96       adiw r26, 0x01
  b6: 01 90       ld r0, Z+          ; c[color=red]opy from init data      [/color]
  b8: 0d 92       st X+, r0          ;   to stack data
  ba: 8a 95       dec r24
  bc: e1 f7       brne .-8       ; 0xb6 (loop for all data.)

  be: fe 01       movw r30, r28        ; [color=red]rest of array is 0s.[/color]
  c0: 3c 96       adiw r30, 0x0c
  c2: 8d eb       ldi r24, 0xBD ; 189 (more bytes)
  c4: df 01       movw r26, r30
  c6: 1d 92       st X+, r1          ; r1 is "known zero"
  c8: 8a 95       dec r24
  ca: e9 f7       brne .-6       ; 0xc6 <testFunc+0x30>

  cc: ce 01       movw r24, r28
  ce: 01 96       adiw r24, 0x01 ; 1
  d0: 0e 94 00 00 call foox
  d4: c8 53       subi r28, 0x38 ; 56
  d6: df 4f       sbci r29, 0xFF ; 255

  d8: 0f b6       in r0, 0x3f ; Restore stack
  da: f8 94       cli
  dc: de bf       out 0x3e, r29 ; 62
  de: 0f be       out 0x3f, r0 ; 63
  e0: cd bf       out 0x3d, r28 ; 61
  e2: df 91       pop r29
  e4: cf 91       pop r28
  e6: 08 95       ret

On the bright side, it looks smart enough to only save the initializers that are actually there.

Thanks for looking at it, I'm impressed by the skills :slight_smile:
But I'm still stuck with my issue, ormaybe I didn't catch it in yours answers, is there a solution to avoid using my SRAM with local variable ?

I dunno. The latest discussion, and my example, were based on an initialized local variable. But that’s not what you had in your first post.

Do you have a smallish example illustrating your original problem?

I’m still thinking it’s an optimization issue, and it’s probably fine the way it is, even if the analysis looks weird.