Arduino Compiler Speicherverwaltung

Gast #5694964
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?
Gast #5694971
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.
Gast #5694975
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));
#5695104
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.
#5695467
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.
Gast #5695714
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.
#5695928
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.
Gast #5696096
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.
Angehängte Dateien:
#5696439
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.
Gast #5696549
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
Gast #5696591
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?
Gast #5696644
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.
Gast #5696946
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.
Gast #5696953
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

Antwort schreiben

Bitte melde dich an, um einen Beitrag zu schreiben.

oder

Mit Google-Account einloggen

Die Registrierung ist kostenlos und dauert nur eine Minute.

Jetzt registrieren