Gratuitious instantiation of `Serial`, why? 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 &lt;Arduino.h&gt;

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?
My guess (though I didn't check) is that it is a side effect of the fact
that the same file contains the instantiation of Serial and the
associated ISRs. The definition of the ISR macro looks roughly
like this (slightly simplified):

#define ISR(vector) extern "C" \
    void __attribute__((signal, used, externally_visible)) vector(void)


The key here is the “used” attribute. It tells the compiler that the
symbol should be considered to be used, even though no code calls the
ISR (for a good reason: it's called by the hardware).

When your program is linked, the object file foo.o pulls in
HardwareSerial0.o. The linker then sees the serial ISRs which are
labeled as “used”. These in turn make use of the Serial object which
then appears to also be used.