Forum: Mikrocontroller und Digitale Elektronik Arduino Compiler Speicherverwaltung


von Lothar (Gast)


Lesenswert?

Kann mir mal jemand die Arduino Compiler Speicherverwaltung erklären?

Ich mache einen leeren Sketch mit folgendem:

void loop()
{
  // volatile unsigned char var1 = 1;
  // volatile unsigned char var2[] = {1, 1};
  // volatile unsigned char var3[] = {1, 1, 1};
}

Ergebnis: "Global variables use 9 bytes"

Das kann ja sein dann gibt es eben einige "versteckte" globale Variable. 
Dann mache ich folgendes:

void loop()
{
  volatile unsigned char var1 = 1;
  volatile unsigned char var2[] = {1, 1};
  // volatile unsigned char var3[] = {1, 1, 1};
}

Ergebnis: "Global variables use 9 bytes"

Auch das ist nachvollziehbar das sind lokale Variablen die beim Aufruf 
auf dem Stack angelegt werden. Nun mache ich noch folgendes:

void loop()
{
  // volatile unsigned char var1 = 1;
  // volatile unsigned char var2[] = {1, 1};
  volatile unsigned char var3[] = {1, 1, 1};
}

Ergebnis "Global variables use 13 bytes"

Wieso das? Reserviert der Compiler für lokale Arrays > 3 Byte globalen 
Speicher? Ist das eine Art Sicherung gegen Stacküberlauf?

von foobar (Gast)


Lesenswert?

Bis zu einer bestimmten Größe werden die Arrays "von Hand", also mit 
einzelnen Instruktionen initialisiert und darüber hinaus mit einer 
Kopierschleife. Das ist Teil des Codegenerators/Optimierers.

von foobar (Gast)


Lesenswert?

Btw, volatile auto vars sind schon ziemlich merkwürdig - da gehört 
zumindest nen Kommentar hinter ;-)

von foobar (Gast)


Lesenswert?

Noch zur Erläuterung: lokale Variablen befinden sich auf dem Stack. Wenn 
sie initialisiert werden sollen, muß das für jeden einzelnen Aufruf vom 
Code händisch gemacht werden.

Bei wenigen Elementen macht der Compiler
1
   // aus
2
   char var[2] = { 1, 2};
3
   // dies
4
   char var[2]; var[0]=1; var[1]=2;

Werden es mehr Elemente (z.B. 5), macht er daraus
1
    static char init937[5] = { 1,2,3,4,5}; // anonymes statisches Array
2
    char var[5]; memcpy(var, init937, sizeof(init937));

von Rufus Τ. F. (rufus) Benutzerseite


Lesenswert?

Und wenn das ganze auf einem AVR läuft, muss für ein normales memcpy 
die Quelle erst mal aus dem Flash ins RAM kopiert werden - das könnte 
die Ursache für den Speicherverbrauch sein; statt memcpy sollte hier 
eigentlich so etwas wie memcpy_P verwendet werden.

von Axel S. (a-za-z0-9)


Lesenswert?

Rufus Τ. F. schrieb:
> Und wenn das ganze auf einem AVR läuft, muss für ein normales /memcpy/
> die Quelle erst mal aus dem Flash ins RAM kopiert werden - das könnte
> die Ursache für den Speicherverbrauch sein; statt memcpy sollte hier
> eigentlich so etwas wie memcpy_P verwendet werden.

Ich bin recht zuversichtlich, daß der Compiler das so organisiert. Wenn 
er es genauer wissen will, muß der TE halt mal ins map-file schauen.

von Rufus Τ. F. (rufus) Benutzerseite


Lesenswert?

Axel S. schrieb:
> Ich bin recht zuversichtlich, daß der Compiler das so organisiert.

Täte er das, wäre der beschriebene Effekt aber nicht feststellbar.

von Axel S. (a-za-z0-9)


Lesenswert?

Rufus Τ. F. schrieb:
> Axel S. schrieb:
>> Ich bin recht zuversichtlich, daß der Compiler das so organisiert.
>
> Täte er das, wäre der beschriebene Effekt aber nicht feststellbar.

Wir wissen gar nicht, was die Ursache für das beschriebene Verhalten 
ist. Wir wissen noch nicht mal, ob der Platz in .data oder .bss 
verbraucht wird. Man könnte das natürlich herausfinden, wenn man 
wöllte. Aber ich bin erstens faul, zweitens beschäftigt und finde es 
drittens auch vollkommen unwichtig.

von Rufus Τ. F. (rufus) Benutzerseite


Lesenswert?

Axel S. schrieb:
> Aber ich bin erstens faul, zweitens beschäftigt und finde es drittens
> auch vollkommen unwichtig.

Dann sind wir uns ja einig ...

von Lothar (Gast)


Lesenswert?

Lothar schrieb:
> Ist das eine Art Sicherung gegen Stacküberlauf?

Ich habe für eine PC-Software eine Funktion zur Datenkomprimierung 
gemacht. Die hat grosse lokale Arrays. Die will ich nun auch auf uC 
nutzen. Zuerst auf 8051: hier ist natürlich klar - die grossen lokalen 
Arrays müssen global gemacht werden da sonst Stacküberlauf. Auf Arduino 
musste ich erstaunt feststellen dass es scheinbar keinen Unterschied 
macht ob die grossen Arrays lokal bleiben oder global gemacht werden. 
Wenn man sich darauf verlassen kann müssten PC Funktionen nicht mehr 
angepasst werden. Wenn nicht würde ich sicherheitshalber auch auf 
Arduino immer alle grossen Arrays global machen. Dann bekomme ich ja vom 
Compiler angezeigt wenn der Speicher voll ist.

foobar schrieb:
> volatile auto vars sind schon ziemlich merkwürdig

Habe ich nur in den Beispielen so gemacht da nicht verwendete Variablen 
sonst wegoptimiert werden.

von Axel S. (a-za-z0-9)


Lesenswert?

Lothar schrieb:
>
> Ich habe für eine PC-Software eine Funktion zur Datenkomprimierung
> gemacht. Die hat grosse lokale Arrays. Die will ich nun auch auf uC
> nutzen. Zuerst auf 8051: hier ist natürlich klar - die grossen lokalen
> Arrays müssen global gemacht werden da sonst Stacküberlauf. Auf Arduino
> musste ich erstaunt feststellen dass es scheinbar keinen Unterschied
> macht ob die grossen Arrays lokal bleiben oder global gemacht werden.

Diese Aussage ist mit Vorsicht zu genießen. "Arduino" ist keine 
einheitliche Hardware-Plattform. Offiziell unterstützt werden AVR und 
Cortex-M Kerne, inoffiziell gibts das Framework für noch mehr. Abhängig 
davon, was da an Hardware auf deinem Arduino ist, hat der Stack entweder 
eine begrenzte Größe oder er kann den ganzen RAM nutzen.

"Große lokale Arrays" klingt aber sowieso falsch. Wenn du die Funktionen 
nicht rekursiv nutzen willst, mach sie wenigstens static. Dann müssen 
sie nicht bei jedem Aufruf der Funktion initialisiert werden.


> ... würde ich sicherheitshalber auch auf
> Arduino immer alle grossen Arrays global machen. Dann bekomme ich
> ja vom Compiler angezeigt wenn der Speicher voll ist.

Zwar nicht vom Compiler, aber ja. Das ist durchaus ein Vorteil.

von Lothar (Gast)


Lesenswert?

Axel S. schrieb:
> "Arduino" ist keine einheitliche Hardware-Plattform

Das Board hat einen ATmega2560

von Stefan F. (Gast)


Angehängte Dateien:

Lesenswert?

Ich habe mal ein Assembler Listing aus der *.elf Datei erstellt, die der 
Compiler (mit dem 3-Byte Array) erzeugt hat:

avr-objdump -h -S /tmp/arduino_build_482746/test.ino.elf > test.txt

Die Ausgabe von
avr-size /tmp/arduino_build_482746/test.ino.elf
ist:
1
   text     data      bss      dec      hex  filename
2
    712        4        9      725      2d5  /tmp/arduino_build_482746/test.ino.elf

Offensichtlich addiert die IDE die Größen vom data und bss Segment 
(siehe https://www.nongnu.org/avr-libc/user-manual/mem_sections.html).

Wenn ich ein komplett leeres Programm compiliere, ist auch bei mir die 
Ausgabe:
1
   text     data      bss      dec      hex  filename
2
    656        0        9      665      299  /tmp/arduino_build_482746/test.ino.elf

Ebenso, wenn ich die beiden kürzeren Arrays einkommentiere.

Ich bin jetzt auch ratlos.

von Axel S. (a-za-z0-9)


Lesenswert?

Stefanus F. schrieb:
> Ich habe mal ein Assembler Listing aus der *.elf Datei erstellt, die der
> Compiler (mit dem 3-Byte Array) erzeugt hat

Ein Mapfile wäre hilfreicher gewesen.
1
LDFLAGS = -Wl,-Map=$(OBJDIR)/$(TARGET).map,--cref

> Offensichtlich addiert die IDE die Größen vom data und bss Segment

Ja. Und noch noinit.
1
AVR Memory Usage
2
----------------
3
...
4
Data:        xxx bytes (yy.y% Full)
5
(.data + .bss + .noinit)


> Wenn ich ein komplett leeres Programm compiliere
>
1
>    text     data      bss      dec      hex  filename
2
>     656        0        9      665      299 
3
>
>
> Ebenso, wenn ich die beiden kürzeren Arrays einkommentiere.
> Ich bin jetzt auch ratlos.

Na ja. Die 9 Bytes bss sind anscheinend nichtininitialisierte 
globale/statische Variablen aus dem Arduino Core. Im Zweifel einfach 
direkt mit dem avr-gcc aus einem .c compilieren. Der Arduino-Krempel 
macht am Ende auch nichts anderes. OK, er compiliert es als C++. Könnte 
auch einen Unterschied ausmachen.

von foobar (Gast)


Lesenswert?

> Ich bin jetzt auch ratlos.

Wieso? Was ist denn noch unklar?  Die 9 Byte im .bss sind timer0_millis, 
timer0_fract und timer0_overflow_count. Und die Initialisierungsdaten 
der auto-Variablen landen im .data.

Hier der Code, den GCC erzeugt (meiner macht erst ab 5 Elementen die 
Kopierschleife; Listing ohne Prolog etc):
1
// void foo() { volatile char var[] = { 1, 2 }; }
2
foo:
3
  ...
4
  ldi r24,lo8(1)
5
  ldi r25,lo8(2)
6
  std Y+2,r25
7
  std Y+1,r24
8
  ...
9
  ret
10
11
// void bar() { volatile char var[] = { 1, 2, 3, 4, 5 }; }
12
  .section  .rodata
13
.LC0:
14
  .byte  1
15
  .byte  2
16
  .byte  3
17
  .byte  4
18
  .byte  5
19
  .text
20
bar:
21
  ...
22
  ldi r24,lo8(5)
23
  ldi r30,lo8(.LC0)
24
  ldi r31,hi8(.LC0)
25
  movw r26,r28
26
  adiw r26,1
27
  0:
28
  ld r0,Z+
29
  st X+,r0
30
  dec r24
31
  brne 0b
32
  ...
33
  ret

von Stefan F. (Gast)


Lesenswert?

Dass das Arduino Framework 9 Bytes belegt, überrascht mich nicht.

Aber das was darüber hinaus geht:
1
volatile unsigned char var1 = 1;            ---> 0 Bytes
2
3
volatile unsigned char var2[] = {1, 1};     ---> 0 Bytes
4
5
volatile unsigned char var3[] = {1, 1, 1};  ---> 4 Bytes

Wie kann das sein?

von foobar (Gast)


Lesenswert?

Das ist einfach ein Aspekt des Codegenerators oder des Optimizers. Wie 
er genau dazu kommt, ist doch Peng.

Ein Entscheidungsgang könnte bei meinem geposteten Kode z.B. sein: er 
braucht 2 Instruktionen pro Arrayelement wenn er jedes Element einzeln 
initialisiert; für die Schleife braucht er 9 Instruktionen, egal wie 
viele Elemente es sind. Also ist es ab 5 Elementen günstiger, die 
Schleife zu benutzen. Ob das jetzt wirklich das Kriterium ist (besonders 
gut ist es ja nicht), weiß ich nicht - dazu müsste man in den GCC 
reinschauen und wird wohl deutlich komplexer sein.

von Peter D. (peda)


Lesenswert?

Lothar schrieb:
> Auf Arduino
> musste ich erstaunt feststellen dass es scheinbar keinen Unterschied
> macht ob die grossen Arrays lokal bleiben oder global gemacht werden.

Der 8051 hat verschiedene RAM-Bereiche (data, xdata), der AVR nicht. Man 
kann auch beim 8051 große Daten lokal ablegen, das ist dann das LARGE 
Speichermodell oder sie als xdata deklarieren.

von Lothar (Gast)


Lesenswert?

Das ist noch eine andere Baustelle. Da der Arduino C++ Compiler 
scheinbar das Codewort flash nicht unterstützt müssten alle 8051 code 
const Array Zugriffe auf pgm_read_byte geändert werden. Da das Aufwand 
ist mache ich die meist auch global - bis das RAM voll ist.

von Einer K. (Gast)


Lesenswert?

Lothar schrieb:
> Da der Arduino C++ Compiler
> scheinbar das Codewort flash nicht unterstützt

Das ist so richtig und auch falsch.

Es gibt keinen Arduino C++ Compiler.
Hat also nichts mit Arduino an sich zu tun.

Es ist wird z.B. der AVR-Gcc genutzt.
Und dort gibt es flash nur für C, nicht in C++


Andere Compiler, für andere µC, mögen das unterstützen.... KA

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.