Forum: Compiler & IDEs LPC2136 mit WinARM schmiert ab bei großen lokalen Arrays


von holger (Gast)


Lesenswert?

Premiere ! Meine erste Frage hier.

Folgendes Problem:

Ich definiere einen lokalen Puffer in main()

#define BUFFER_SIZE  8192

int main(void)
{
 unsigned char buffer[BUFFER_SIZE];
.....

Das Programm schmiert ab. Sieht nach Stack Überlauf aus.
Je nachdem wie groß BUFFER_SIZE definiert wird stürzt
es an unterschiedlichen Stellen ab.
Mit BUFFER_SIZE 512 läuft es ohne Probleme.

Wird buffer[] global definiert läuft alles einwandfrei !

#define BUFFER_SIZE  8192
unsigned char buffer[BUFFER_SIZE];

int main(void)
{
.....


Der LPC2136 hat 32kB RAM, und mein Programm braucht nicht mal
1,3kB davon. Abgesehen vom großen buffer[]. Zusammen also
sagen wir mal 10kB.

Mit lokalem buffer[] bekomme ich folgende Speicherbelegung:

.text             18508            0
.data                 4   1073741824
.bss               1316   1073741828
.stack             1024   1073743360

Mit globalem buffer[] sieht das so aus:

.text             18440            0
.data                 4   1073741824
.bss               9508   1073741828
.stack             1024   1073751552

Werden lokale Puffer auf dem Stack angelegt ?
Das würde die Abstürze erklären.

von Micha (Gast)


Lesenswert?

Hi Holger,

sofern du nicht static davor schreibst, liegt die Variable i.allg. auf 
dem Stack (machen zumindest C Compiler so).
Mit static kann sie dort eigentlich nicht mehr liegen, weil der Wert der 
Variablen zwischen Aufrufen erhalten bleibt (d.h. diese Variable kann 
nur durch diese Funktion verändert werden).

von Micha (Gast)


Lesenswert?

P.S.: auf den Heap kannst du in C nur mit Hilfe der Funktionen für die 
dynamische Speicherverwaltung zugreifen. Das Stackgeschiebe rechnet der 
Compiler beim Übersetzen der Funktion aus. Wieviel Stack jedoch 
verfügbar ist, wird normalerweise erst zur Laufzeit festgelegt. Im 
allgemeinen ist zum Übersetzungszeitpunkt noch nicht klar, wie die 
Laufzeitumgebungen einer Funktion dimensioniert sind.

von holger (Gast)


Lesenswert?

Hallo Micha,

mit static funktioniert es jetzt auch lokal.
Und der reale Speicherbedarf wird auch angezeigt.


.text             18440            0
.data                 4   1073741824
.bss               9508   1073741828
.stack             1024   1073751552

DANKE !

von Mark .. (mork)


Lesenswert?

Hallo,

ich hatte vor kurzen das gleiche Problem. Aber ich versteh immer nocht 
nicht, wie das sein kann. Wenn der Stack nach unten wächst, dann belegt 
er imer mehr Platz des Heaps, bis dieser ausgeht. Erst dann kommt es zu 
einer Kollosion zwischen dem Stack und den statischen Variablen. Wenn 
der LPC2136 32kb RAM hat und wie oben 1316 Bytes für statische Variablen 
braucht und 1024 Bytes für den Stack berechnet wurden, denn bleibt ja 
theoretisch 32768-1316-1024=30428 Bytes übrich für den Heap. Wie kann es 
dann sein, dass der Stack in .bss reinwächst?

MfG Mark

von Andreas K. (a-k)


Lesenswert?

WinARM selbst definiert kein Speicherlayout, das bleibt dem Anwender mit 
seinem individuellen Startup-Code und Linker-Script überlassen. Da 
holger darüber nicht informiert hat, kann hier nur spekuliert werden.

von holger (Gast)


Angehängte Dateien:

Lesenswert?

Hier ist mein komplettes Projekt.

Der Stack ist fix auf 1024 Bytes definiert.
Ich hab noch nicht rausgefunden wo.

Viel Spaß beim suchen ;)

von Rufus Τ. F. (rufus) Benutzerseite


Lesenswert?

Wenn Du Dein Array innerhalb der Funktion deklarierst, ist es eine 
sogenannte "automatische Variable". Und die landen nicht auf dem Heap, 
sondern auf dem Stack.
Einen Heap gibt es erst, wenn dynamische Speicherverwaltung mit 
malloc/new durchgeführt wird.

Dein Stack ist 1 kByte groß, Deine Variable aber ist 8 kByte groß.

Variablen, die außerhalb einer Funktion deklariert werden, sind keine 
automatischen Variablen und landen daher nicht auf dem Stack. Ebenso 
statische Variablen, die innerhalb einer Funktion deklariert werden.

von Andreas K. (a-k)


Lesenswert?

Beim Stack hat man die Wahl wohin man ihn legt.

- Eine gängige Version ist die von Mark skizzierte - am oberen Ende des 
Speichers anfangend abwärts gegen den Heap laufend. Vorteil: Sowohl Heap 
wie auch (System/User-) Stack können den gesamten Speicher nutzen, ohne 
vorher Limits festlegen zu müssen.

- Die andere Variante legt ihn ans Ende des definierten Datenbereichs, 
und den Heap dahinter. Das ist die hier verwendete. Damit ist die Grösse 
aller Stacks zusammen entsprechend begrenzt.

Entschieden wird das im Zusammenspiel von Linker-Script und 
Startup-Code.

von Mark .. (mork)


Lesenswert?

Hallo Andreas,

vielen Dank für die Erklärung. Jetzt verstehe ich auch das Problem, denn 
ich wusste nicht, dass es so etwas wie die zweite von Dir geschilderte 
Variante gibt. Wie kann man denn im Linkerscript einstellen, dass der 
Stack ganz am Ende platziert werden muss?

MfG Mark

von Andreas K. (a-k)


Lesenswert?

> Wie kann man denn im Linkerscript einstellen, dass der
> Stack ganz am Ende platziert werden muss?

Der Stack wird im Startup-Code geladen. Entscheidend ist womit. In hier 
gezeigten Fall wird das Symbol "_stack" dazu verwendet, das im 
Linker-Script am Ende der .stack Section plaziert wird.

Alternativ kann man im Linker-Script die .stack Section auch ganz 
weglassen, und das entsprechende Symbol statt dessen direkt ans Ende vom 
RAM nageln:
  _stack = 0x40008000;

Wobei man das im Startup-Code auch direkt so machen kann. Ich persönlich 
neige jedoch dazu, den Startup-Code innerhalb der LPC2000-Reihe gleich 
zu halten und die Abhängigkeiten vom jeweiligen Modell in Linker-Scripte 
zu verlagern. Wobei ich das Linker-Script aufteile:

LPC2119.ld LPC2129.ld LPC2106.ld ... definert die MEMORY Areas und eben 
des Anfang der Stacks (5-zeiler).

ROM.ld RAM.ld definiert basierend darauf die Sections (ROM-basiert und 
RAM-basiert) und ist modellunabhängig. Im Makefile stehen dann einfach 
beide Scripte nacheinander drin.

von Micha (Gast)


Lesenswert?

Auch wenn der Stack ganz am Ende anfängt, kann allzu bedenkenloser 
Umgang mit Stack Ärger verursachen: nämlich dann, wenn mehrere Tasks 
laufen und es keine Stacküberwachung gibt (häufig). Bei Erzeugen der 
Task muss normalerweise die Grösse des Stacks angegeben werden, mit dem 
die Task auskommen muss. Der Stack der nächsten Task fängt dann 
mittelbar (z.B. wg.TCB) oder unmittelbar danach an. Die Task die über 
ihren Stack hinausschreibt arbeitet dann zwar noch korrekt, aber sobald 
die Task, deren Stack sich anschliesst und der überschrieben wurde, den 
Prozessor zugeteilt bekommt, kommt's zu unvorhersehbarem Verhalten bzw. 
Abstürzen.

von mgiaco (Gast)


Lesenswert?

@Andreas kannst du mal so eine Linkerfile hier reinstellen.

mgiaco

von Andreas K. (a-k)


Angehängte Dateien:

Lesenswert?

yep

von mgiaco (Gast)


Lesenswert?

danke. muss es mal mit dem was ich habe vergleichen.

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.