And finding some surprising, and confusing, things. This thread will likely be of little interest of most people, who will, if lucky, never NEED to learn the things I'm trying to learn. I am trying to get a solid understanding of exactly how memory allocation is done in the Arduino, how the compiler estimates memory usage, some of the optimizations being performed, and how to achieve a minimal memory footprint. So, this will be something of a stream-of-consciousness thread, as I report what I see and learn (and, no doubt at times, as I get more and more confused...). One thing that is already clear, is the compiler optimizations are quite impressive! So, here goes...
Trying to shoe-horn some code into a 328, I'm struggling to get RAM usage down. Right now, I'm trying to make sense of exactly how RAM is allocated, reported, and used, as I sometimes find HUGE amounts of RAM disappearing for no good reason that I can see. I am sure there is good reason, and I want to understand what that reason is. I am finding it is even stranger, and more complex, than expected. Here is a surprising, albeit simple, example I stumbled across by accident:
First, I modified the freeMemory library to add a new function to just dump what's there:
void showMemory(const char *s)
{
char __stack = 0;
Serial.println(s);
Serial.print("__brkval=");
Serial.println((unsigned int)__brkval);
Serial.print("__flp=");
Serial.println((unsigned int)__flp);
Serial.print("__stack=");
Serial.println((unsigned int)&__stack);
Serial.print("stack size=");
Serial.println(RAM_end - (unsigned int)&__stack);
Serial.print("Heap size=");
Serial.println((unsigned int)__brkval - RAM_start);
struct __freelist* current;
int total = 0;
for (current = __flp; current; current = current->nx)
{
total += 2; /* Add two bytes for the memory block's header */
total += (int) current->sz;
Serial.print("mblk: sz=");
Serial.print((unsigned int)current->sz);
Serial.print(" nx=");
Serial.println((unsigned int)current->nx);
Serial.print("Total: ");
Serial.println(total);
}
Serial.println("\n");
}
So, that shows be where the "break" between stack and heap is, and the current stack pointer. Note than in a 328, RAM starts at address 0x100 (256 DEC), and ends at address 0x8FF (2303 DEC).
First, I compile and run this trivial sketch:
void setup(void)
{
Serial.begin(38400);
Serial.print(F("Starting...\n\n"));
showMemory(F("Initial"));
}
void loop()
{
}
This produces the following output:
Starting...
Initial
__brkval=0
__flp=0
__stack=2299
stack size=4
Heap size=65280
That mostly makes sense. The max stack size is 4 bytes, from 2299->2303. I don't understand __brkval being 0, especially given that the compiler memory report indicates "436 bytes of dynamic memory used". But, __brkval being 0 is why the displayed "heap size" is obviously wrong, since the compiler reports something north of 400 bytes of RAM used. We'll come back to that later.
Now, I add a few more lines of code to the sketch:
const char *cfgFile = "temp.cfg";
void setup(void)
{
Serial.begin(38400);
Serial.print(F("Starting...\n\n"));
showMemory(F("Initial"));
if(SPIFFS.exist(cfgFile))
Serial.println(F("cfgfile exists"));
else
Serial.println(F("cfgFile does not exist"));
showMemory(F("Final"));
}
void loop()
{
}
Now, notice a few things:
There is a call to SPIFFS.exists(), which is a static member function of my FS library. The functionality of this library, and function, is not important at the moment. When I compiled this sketch, I expected to get a compiler error, since SPIFFS is not really defined anywhere in the sketch. To my surprise, it compiled without error! Here's why:
First, as I said, exists() is a static member function, so no instance of FS is required, to make the function usable. But how does it find THAT exists(), when there is no instance of FS named SPIFFS? The answer is, there is an "extern" statement in FS.h, which tells the compiler there should be an instance of FS named SPIFFS somewhere, so it makes that connection. Since the function is static, it doesn't require and since of the class, so both compiler and linker are happy! This really surprised me!
So, what happens when that sketch is run? This:
Starting...
Initial
__brkval=0
__flp=0
__stack=2252
stack size=51
Heap size=65280
exists("temp.cfg")
File 0 does match
cfgfile exists
Final
__brkval=0
__flp=0
__stack=2252
stack size=51
Heap size=65280
Stack size has increased from 4 to 51 bytes, a difference of 47 bytes. Most of this can be explained by the fact that exists has 34 bytes of local variables. I'm not perfectly clear on where the other 17 bytes went, but exists calls some other static functions within FS, and also the EEPROM library, so it is safe to assume those 17 bytes are to accommodate the stack needs of those functions. But, note __brkval is still 0! What's up with that?
Let's add two more lines to the sketch:
void setup(void)
{
Serial.begin(38400);
Serial.print(F("Starting...\n\n"));
showMemory(F("Initial"));
FS myFS1 = FS();
myFS1.dump();
showMemory(F("Final"));
}
The call to FS::dump() is only there to prevent myFS1 from being optimized away. This produces:
Starting...
Initial
__brkval=0
__flp=0
__stack=2249
stack size=54
Heap size=65280
// FS:dump() output deleted
Final
__brkval=722
__flp=0
__stack=2249
stack size=54
Heap size=466
The stack has moved down by 3 bytes, the exact size of the member data in FS, which makes perfect sense, since FS is created on the stack, as a local variable. And, what do you know, suddenly, __brkval is changed also! To 722??? As it turns out, 722 = 466 + 256. 256 is the start address of RAM in the 328P, and 466 is the exact RAM size reported by the compiler. So, now the heap starts at address 256 and ends at address 722. But why does __brkval change at all? myFS1 is created on the stack, as a local variable, so should have no impact on the heap!
Let's try this again, but this time put myFS1 in the heap:
void setup(void)
{
Serial.begin(38400);
Serial.print(F("Starting...\n\n"));
showMemory(F("Initial"));
FS *myFS1 = new FS();
myFS1->dump();
showMemory(F("Final"));
}
Here's what we get:
Starting...
Initial
__brkval=0
__flp=0
__stack=2252
stack size=51
Heap size=65280
// FS::dump() output deleted
Final
__brkval=727
__flp=0
__stack=2252
stack size=51
Heap size=471
Stack size has been reduced by 3 bytes, since the FS member data is now stored on the stack. Heap size has been reduced by 5 bytes. 3 of those are the FS member data, and other 2 bytes are, I guess, the linked list pointer to the next memory block?
In any case, both of these sketches change __brkval from 0 (an illogical value) to a sensible, non-zero value? So, why is __brkval EVER set to 0? What does that signify?
Let's try one more thing:
FS myFS = FS();
void setup(void)
{
Serial.begin(38400);
Serial.print(F("Starting...\n\n"));
showMemory(F("Initial"));
myFS.dump();
showMemory(F("After myFS.dump()"));
FS myFS1 = FS();
myFS1.dump();
showMemory(F("Final"));
}
[/code]
Here, I've added a second instance of FS, in global space. But the showMemory response is really surprising:
Starting...
Initial
__brkval=0
__flp=0
__stack=2284
stack size=19
Heap size=65280
// FS:dump() output deleted
Final
__brkval=743
__flp=0
__stack=2284
stack size=19
Heap size=487
OK..... __brkval is zero, UNTIL FS:dump() is called??? WTF??? What is triggering the change to __brkval?
To be continued...
Regards,
Ray L.