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?
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.
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
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.
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.
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.
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.
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.
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.
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:
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.
> 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):
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.
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.
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.
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