Let's take a very simple sketch, any simple one-.ino sketch. Say, just a led blinker like this
void setup()
{
pinMode(LED_BUILTIN, OUTPUT);
digitalWrite(LED_BUILTIN, LOW);
}
void loop()
{
static bool on;
digitalWrite(LED_BUILTIN, (on = !on) ? HIGH : LOW);
delay(1000);
}
Once compiled in Arduino IDE, compiler reports using 10 bytes for global variables. This is perfect for our purposes.
Now let's add an extra .cpp
file to it. Let's call it foo.cpp
. The file looks like follows
#include <Arduino.h>
int foo()
{
return Serial.available();
}
Compile it all together. Now the compiler reports using 185 bytes for globals. But why?!
Clearly, this extra global memory usage is Serial
. It is brought in by the fact that global object Serial
is referenced from foo
. In the original sketch Serial
is not instantiated (or discarded) by the linker as an unreferenced symbol. In this case it is referenced from foo
, so the linker keeps it.
But... But GCC linker is usually smarter than this. I'd expect it to see that symbol foo
is not referenced anywhere, so the reference to Serial
inside foo
also "doesn't count" and should not require an instance of Serial
in final code.
In fact, if I tried using my own "heavy" class, my own global variable of that class and reference it in foo
(instead of Serial
), GCC would work as expected: regardless of how "heavy" my global variable is, GCC linker would discard my global variable from the final code and report the same 10 bytes for globals, as expected. The global memory usage would remain at 10 until I'd actually reference foo
from somewhere.
But Serial
is somehow special. Even an "unused" mention of Serial
causes it to appear in global memory. What is so special about it? What makes it behave that way?
volatile
in the implementation of SerialSerial
makes volatile access in its constructor, then it should always be preserved, regardless of whether the client code usesSerial
or not. This is not the behavior we are observing.objdump
is a big help here.