1

I am facing a low memory available problem in arduino. I am compiling a big sketch for arduino mega 2560.

Analysing a .elf file, the avr-size tool gives:

text    data     bss     dec     hex filename
68524   4392    2560   75476   126d4 C:\Users\LEAN...

And the avr-nm (avr-nm doc) shows only variables with t, T, b, d and B. The following is how the command is executed (the output is being saved in a file called test.txt).

   avr-nm -a -td -C -l --size-sort -r x.elf >> test.txt

Then, I perform a sum to confirm the avr-size result using this Python script:

f = open("test.txt", "r")
lines = f.readlines()
f.close()

su = 0
dsum = 0
bsum = 0
tsum = 0
gsum = 0
for line in lines:
    #print(line, end="")
    s = line.split()
    try:
        val = int(s[0])
        su = su + val
        if(s[1].lower() == 'b'):
            bsum = bsum + val
        if(s[1].lower() == 'd'):
            print(line)
            dsum = dsum + val
        if(s[1].lower() == 't'):
            tsum = tsum + val
        if(s[1].lower() == 'g'):
            gsum = gsum + val
    except:
        print("ERR: ",s[0])
print("\n\nSIZE: ", su)
print("SIZE d/D: ", dsum)
print("SIZE b/B: ", bsum)
print("SIZE t/T: ", tsum)
print("SIZE g/G: ", gsum)

and the sum result of each var type is:

SIZE:  71015
SIZE d/D:  135
SIZE b/B:  2560
SIZE t/T:  68320
SIZE g/G:  0

The question: Here d/D sums is 135 bytes. So, why the data segment reported by avr-size is 4392 bytes? how to find out what is occupying this space?

Best Regards.

EDIT

It must be a Joke!

I have a lot of like this in the code:

Serial.print("QTDE REG: ");

Just changing for this:

Serial.print(F("QTDE REG: "));

I have reduced by 10 the .data size!

So, in this way Serial.print("QTDE REG: "); "QTDE REG: " is stored as a global variable?? I thought the compiler would save locally.

EDIT 2

I have an advance running avr-objdump -s -j .data x.elf as suggested by Edgar below. Now I see that all my string literals are in data! For example:

void myFunction(){
    ...
    sprintf(buf, "{\"name\":\"Intervalo de Registros\",\"value\":\"%d\", \"unit\":\"C\"},", (int)settings.treg);
    ...
}

avr-objdump -s -j .data x.elf shows:

 800660 2c007b22 6e616d65 223a2249 6e746572  ,.{"name":"Inter
 800670 76616c6f 20646520 52656769 7374726f  valo de Registro
 800680 73222c22 76616c75 65223a22 2564222c  s","value":"%d",
 800690 2022756e 6974223a 2243227d 2c007b22   "unit":"C"},.{"

EDIT 3

As commented, all string literals are saved in the (SRAM). So, the problem is, I have a lot of strings literals spread across different functions. How to save this amount of SRAM? Note that using F() for sprintf gives compilation error.

Is there any way to put the literal in stack (release memory when function returns) and not in the global area? Is F() the only solution?

============ SOLUTION ============

For now, I have found this solution: use sprintf_P with PSTR(...) in the same way of F(...):

sprintf_P(buf, PSTR("{\"name\":\"Intervalo de Registros\",\"value\":\"%d\", \"unit\":\"C\"},"), (int)settings.treg);

I will test more...

Thanks for all clarification!

Best Regards!

4
  • all the string literals are copied to SRAM with older versions of avr gcc. F macro ensures they are read from flash.
    – Juraj
    Commented Jul 2, 2020 at 18:02
  • @Juraj but when the function returns the space allocated is released? seems that all my literals goes to data segment, which is a place for global vars.
    – LeandroIP
    Commented Jul 2, 2020 at 19:01
  • sorry, it should be "all the string literals are copied to SRAM at boot"
    – Juraj
    Commented Jul 3, 2020 at 3:10

1 Answer 1

2

Most of the missing data section is probably made of string literals. When you write something like

Serial.println("Hello, World!");

the string "Hello, World!" is implemented by the compiler as an anonymous array of characters, and the address of this array is provided to the println() method. Since the array is anonymous, it doesn't show up in the output of avr-nm. You can check that this is the case by looking at the actual contents of the section:

avr-objdump -s -j .data x.elf

And you can avoid this by wrapping the strings in the F() macro:

Serial.println(F("Hello, World!"));

This has the effect of keeping the string in flash, and passing the flash address to the println() method. Note that this is actually a different, overloaded method, that knows how to retrieve the character data from flash.


Edit: Adding some clarifications.

All this complexity about flash vs. RAM storage comes from the fact that the AVR uses a Harvard architecture, where RAM and flash are in different address spaces, accessed through different memory buses using different machine instructions. Unlike C, the C++ language has no notion of address spaces, and the compiler simply assumes that any pointer to function holds a flash address, whereas any pointer to data holds a RAM address.

Per the rules of the language, a string literal has the type const char *, and a function that should be able to accept a string literal should take a const char * parameter. Since these are pointers to data, when you dereference them the compiler emits the machine instructions that are appropriate for reading RAM. This is why the compiler puts all string literals in the .data section of the flash: at program startup, the C runtime copies them to the .data section of the RAM, making them accessible to those machine instructions.

The current solution to avoid this useless copy involves a bunch of preprocessor macros like F(), PSTR(), PROGMEM, pgm_read_byte()... that expand to compiler extensions or inline assembly. These allow you to pass around the flash addresses of string literals disguised as regular data pointers. These pointers should never be dereferenced with the regular operators of the language (* and []), but only through the pgm_read_*() macros. And they should only be given to functions that do expect these kind of pointers, like sprintf_P() or Print::println(const __FlashStringHelper *), which is the println() that gets invoked when using the F() macro.

Is there any way to put the literal in stack

Yes, there is, but it has to be done explicitly:

PROGMEM const char greeting[] = "Hello, World!";

void say_hello() {
    char stack_copy[strlen_P(greeting)+1];
    strcpy_P(stack_copy, greeting);
    Serial.println(stack_copy);
}

As you have already noticed, it is preferable to not put the strings in RAM at all, and instead use functions like sprintf_P that expect flash-based strings. However, if you need to use a library function that expects a RAM-based string, you can use this trick to avoid permanently committing it to RAM.

3
  • Using F() on println saves me about 100bytes. Executing avr-objdump as sugested I see the string of sprintf(buf, "{\"name\":\"Intervalo de Registros\",\"value\":\"%d\", \"unit\":\"C\"},", (int)settings.treg); in it. All strrings are stored in this area?
    – LeandroIP
    Commented Jul 2, 2020 at 18:58
  • @LeandroIP: all the string literals, unless using the F() macro or, equivalently, PSTR(). Commented Jul 2, 2020 at 19:58
  • Thanks @Edgar, PSTR() solves the problem with sprintf_P. See SOLUTION in answer for details.
    – LeandroIP
    Commented Jul 2, 2020 at 20:26

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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