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!
-
all the string literals are copied to SRAM with older versions of avr gcc. F macro ensures they are read from flash.Juraj– Juraj ♦2020年07月02日 18:02:24 +00:00Commented 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– LeandroIP2020年07月02日 19:01:51 +00:00Commented Jul 2, 2020 at 19:01
-
sorry, it should be "all the string literals are copied to SRAM at boot"Juraj– Juraj ♦2020年07月03日 03:10:42 +00:00Commented Jul 3, 2020 at 3:10
-
arduino.cc/reference/en/language/variables/utilities/progmem/…Juraj– Juraj ♦2020年07月03日 03:14:16 +00:00Commented Jul 3, 2020 at 3:14
1 Answer 1
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.
-
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– LeandroIP2020年07月02日 18:58:31 +00:00Commented Jul 2, 2020 at 18:58 -
@LeandroIP: all the string literals, unless using the
F()
macro or, equivalently,PSTR()
.Edgar Bonet– Edgar Bonet2020年07月02日 19:58:56 +00:00Commented Jul 2, 2020 at 19:58 -
Thanks @Edgar, PSTR() solves the problem with sprintf_P. See SOLUTION in answer for details.LeandroIP– LeandroIP2020年07月02日 20:26:34 +00:00Commented Jul 2, 2020 at 20:26
Explore related questions
See similar questions with these tags.