1

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?

  • I am sure there is some volatile in the implementation of Serial – Juraj Apr 20 at 5:08
  • @Juraj: What specific occurrence of volatile do you have in mind? The compiler is supposed to preserve each occurrence of volatile access. But if the function is never called, there's no access to preserve. If Serial makes volatile access in its constructor, then it should always be preserved, regardless of whether the client code uses Serial or not. This is not the behavior we are observing. – AnT Apr 20 at 6:25
  • Did you check the command line for compiler and linker? There might be some option that stops the linker from discarding. And did you look into the object files to see what uses the space? objdump is a big help here. – the busybee Apr 20 at 7:34
  • at linking HardwareSerial.o is in an .a archive. if it would by a single .o on linker command line it would be always linked. if it is in .a it is linked only if it is required. this is not an answer, only a lead. I don't know how 'required' is determined – Juraj Apr 20 at 8:02
2

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.

| improve this answer | |
  • 1
    In my original program, foo.cpp is actually a part of my Arduino library. AFAIK, during Arduino build process libraries are compiled and archived into genuine static libraries. And then object files are pulled out of them only if they are needed (per standard ln behavior). So, in my example foo.o should not get pulled out of the library and therefore should not pull HardwareSerial0.o with it. However, the strange behavior still persists: once the library is used by a sketch, Serial immediately grabs a chunk of memory. – AnT Apr 21 at 7:14
  • @AnT, has your library dot_a_linkage in library.properties? without it it .o files are put on linker command line – Juraj Apr 21 at 15:24
  • @Juraj: No, it did not have that. And this actually solves the problem immediately. – AnT Apr 21 at 15:52
0

I compiled the examples with the microCore/attiny13 which has a different Serial implementation there the compiler behaves as expected.

So reducing in HardwareSerial.h the

 #define SERIAL_RX_BUFFER_SIZE 64

 to

#define SERIAL_RX_BUFFER_SIZE 0

does exactly 121 bytes used (which is 185-64) the same for

 #define SERIAL_TX_BUFFER_SIZE 64

 to

#define SERIAL_TX_BUFFER_SIZE 0

gives 57 bytes (121-64). This is used by Stream protected:

unsigned char _rx_buffer[SERIAL_RX_BUFFER_SIZE];
unsigned char _tx_buffer[SERIAL_TX_BUFFER_SIZE];

I did not go through all the used libs to find the rest, but I guess its the global defines (buffers) for the global object Serial. The microCore lib has all buffers set to 0 so thats how I found it.

| improve this answer | |

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.