Forum: Mikrocontroller und Digitale Elektronik Stack richtig dimensionieren


von martin (Gast)


Lesenswert?

Ist es richtig, dass alles was in einer geschweiften Klammer als 
Variable definiert ist, nicht als static declariert und nicht dynamisch 
angelegt ist, auf dem Stack landet, daneben noch Ein- und 
Rücksprungadressen der Funktionen.

Wie seht ihr das konstruierte unten stehende Beispiel.
Habe ich das soweit richtig verstanden oder passt manches nicht?
Entscheidet der Compiler mit?
Er könnte ja wissen, was alles auf den Stack kommt.
Wenn er das weiß, und die festgelegte Stackgröße kennt, gibt er eine 
Fehlermeldung aus, wenn diese beim compilieren überschritten wird?

Gibt es eine Faustformel für die Stackgröße?


Beispiel, wie ich das verstanden habe für Programmiersprache C und C++:
1
int array_A[100];    // Das landet nicht auf dem Stack.
2
                     // Static kommt wohl von Compiler hinzu.
3
4
static array_B[100]; // Das landet nicht auf dem Stack.
5
6
int main(void)
7
{
8
  
9
  // Das landet vermutlich auf dem Stack. Bin mir aber nicht sicher.
10
  int array_C[100];
11
12
  // Das landet vermutlich "nicht" auf dem Stack wegen static.
13
  static int array_D[100];   
14
  
15
  for(;;) // Endlosschleife. Wird vom Programm nicht verlassen
16
  {
17
18
    // z Wird wohl permanent auf den Stack rauf und runtergenommen.
19
    int z = 2;  
20
21
    // Die unterschiedlichen Übergabemöglichkeiten sind nur Spielerei.
22
    // Sollten aber wohl zulässig sein. 
23
    // Es wird nur die Referenz auf die Arrays übergeben.
24
    // z wird kopiert
25
    funktion(array_A, array_B, &array_C[0], &array_D[0], z);
26
27
  }
28
29
}
30
31
void funktion(int a[], int b[], int *p_c, int d[], int y)
32
{
33
  int array_E[100];        // Kommt auf den Stack und bei verlassen der 
34
                           // Funktion wieder runter.
35
  static int array_F[100]; // Ist nicht auf dem Stack.
36
37
  // a, b, p_c, d, y kommen auf den Stack
38
  // Beim Verlassen der Funktion werden sie vom Stack genommen.  
39
}

von Jim M. (turboj)


Lesenswert?

martin schrieb:
> Entscheidet der Compiler mit?

Ja, aber nur auf exotischen Plattformen wie 8051, wo der Stack extrem 
klein ist. Dort kann er Sachen ins (X-)Data Segent packen - braucht 
dafür aber eine Analyse des Programms (Stichwort: Data Overlay).

martin schrieb:
> gibt er eine
> Fehlermeldung aus, wenn diese beim compilieren überschritten wird?

In aller Regel nicht. Nitpick: Außerdem weiss der Compiler nicht wie 
groß der Speicher ist, sondern nur der Linker.

martin schrieb:
> Gibt es eine Faustformel für die Stackgröße?

Größe der lokalen Variablen + Rücksprung Addresse, zuzüglich 
Unterfunktionen. Oh, und auf einem µC mit verschachtelten Interrupts 
kommen die natürlich noch dazu.

von MaLin (Gast)


Lesenswert?

martin schrieb:
> Wenn er das weiß, und die festgelegte Stackgröße kennt, gibt er eine
> Fehlermeldung aus, wenn diese beim compilieren überschritten wird?


-Wstack-usage=...

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


Lesenswert?

martin schrieb:
> Ist es richtig, dass alles was in einer geschweiften Klammer als
> Variable definiert ist, nicht als static declariert und nicht dynamisch
> angelegt ist, auf dem Stack landet, daneben noch Ein- und
> Rücksprungadressen der Funktionen.

Es ist für andere viel leichter, deine Ergüsse zu lesen, wenn du Fragen 
auch mit einem Fragezeichen beendest. Und nein, das ist nicht (komplett) 
richtig. Aber auch nicht komplett falsch.

Beispielsweise kommt die Einsprungadresse einer Funktion nie auf den 
Stack (wozu auch?). Und Rücksprungadresse und Daten müssen nicht auf dem 
selben Stack liegen. Viele Architekturen bieten mehrere Stacks. Andere 
haben nur einen sehr beschränkten Returnstack, weswegen der Compiler 
einen Datenstack in Software implementiert. Details hängen extrem von 
der Zielarchitektur ab.

> Entscheidet der Compiler mit?

Immer.

> Wenn er das weiß, und die festgelegte Stackgröße kennt, gibt er eine
> Fehlermeldung aus, wenn diese beim compilieren überschritten wird?

Der Compiler weiß für gewöhnlich nicht, wie groß der Stack einmal sein 
wird.

> Gibt es eine Faustformel für die Stackgröße?

Nein. Denn die hängt bei nichttrivialen Programmen meist auch von den 
Eingabedaten ab. Einfachstes Beispiel: eine rekursive Funktion, z.B. für 
die Berecnnung der Fibonacci-Zahlen.

Statische Ermittlung der Stackgröße ist nur möglich, wenn man auf 
(legale) Sprachmittel wie z.B. Rekursion verzichtet.

> int array_A[100];    // Das landet nicht auf dem Stack.
>                      // Static kommt wohl von Compiler hinzu.

Hier kommt natürlich kein static hinzu.

> static array_B[100]; // Das landet nicht auf dem Stack.

Bei globalen Variablen ist static bedeutungslos.

> int main(void)
> {
>
>   // Das landet vermutlich auf dem Stack. Bin mir aber nicht sicher.
>   int array_C[100];

Hier kann der Compiler frei entscheiden.

>   // Das landet vermutlich "nicht" auf dem Stack wegen static.
>   static int array_D[100];

static Variablen genauso wie globale Variablen können nicht auf dem 
Stack liegen, weil ihre Lebenszeit das gesamte Programm ist. Stackframes 
haben aber immer eine beschränkte Lebenszeit - vom Eintritt in den bis 
zum Verlassen des zugehörigen Blocks.

>   for(;;) // Endlosschleife. Wird vom Programm nicht verlassen
>   {
>
>     // z Wird wohl permanent auf den Stack rauf und runtergenommen.
>     int z = 2;

Nicht notwendigerweise. Könnte genauso gut permanent in einem Register 
gehalten werden. Der Compiler entscheidet.

> void funktion(int a[], int b[], int *p_c, int d[], int y)
> {
>   int array_E[100];        // Kommt auf den Stack und bei verlassen der
>                            // Funktion wieder runter.

nicht notwendigerweise, s.o.

>   static int array_F[100]; // Ist nicht auf dem Stack.

Korrekt. s.o.

>   // a, b, p_c, d, y kommen auf den Stack

Oder sind permanent in Registern. s.o.

von Jakob (Gast)


Lesenswert?

Tja, kommt wohl auf die Zielarchitektur an...


Klar kann man sich da 3, oder auch 27 Stacks bereitstellen
lassen.

Fragt sich nur, ob das verfügbare RAM genug Platz bietet:
Bei manchem AVR-Tiny sind nur 128 Byte für arrays,
Zwischenspeicher für Register und Rücksprungadressen
vorhanden. Auch bei den kleinen AVR-Mega sind es nur
wenige Kbyte...

Also - was bietet der µC deiner Wahl an RAM???

von martin (Gast)


Lesenswert?

128k

von Wolfgang (Gast)


Lesenswert?

martin schrieb:
> Ist es richtig, dass alles was in einer geschweiften Klammer als
> Variable definiert ist, nicht als static declariert und nicht dynamisch
> angelegt ist, auf dem Stack landet

Nein, der Compiler kann auch beschließen, dass er solche Variablen in 
Register packt.

von Omega G. (omega) Benutzerseite


Lesenswert?

Axel S. schrieb:
>> int array_A[100];    // Das landet nicht auf dem Stack.
>>                      // Static kommt wohl von Compiler hinzu.
>
> Hier kommt natürlich kein static hinzu.
>
>> static array_B[100]; // Das landet nicht auf dem Stack.
>
> Bei globalen Variablen ist static bedeutungslos.

Da muss ich dir teilweise wiedersprechen. Static ist bei globalen 
Variablen nicht bedeutungslos, hat aber eine ganz andere Bedeutung als 
in Funktionen.

Bei globalen Variablen und Funktionen hat static Einfluss auf die 
Sichtbarkeit beim linken. Static bewirkt eine lokale Sichtbarkeit, 
also innerhalb des Moduls bzw. der Datei und nicht in anderen Dateien.

Siehe: 
https://stackoverflow.com/questions/4239834/global-variable-in-c-are-static-or-not

von 23456789765435678987654356789 (Gast)


Lesenswert?

compilerflags setzen:

-fstack-usage -Wstack-usage=512

wobei 512 eine stackgröße ist

das ergibt eine Warnung bei übercshreiten der 512ytes
wenn du nur 512byte stack hast , setzt du vieleicht lieber 400bytes an

dann bleibt auch für ISR und so genug luft

fstack-usage erzeugt beim compilieren zusätzliche dateien

wenn man nun das durchsucht iwrd für jede funktion ene stackgröße 
angegeben
auch für ISRs und so

so findet an ggf stackfresser

von 23456789765435678987654356789 (Gast)


Lesenswert?

achso ..

die dateien heißen .su am ende

zB: sieht es dann so aus:

ethernetif.c:163:14:low_level_output  48  static
ethernetif.c:354:6:ethernetif_input  24  static
ethernetif.c:139:6:ethernet_close  8  static
ethernetif.c:332:6:HAL_ETH_RxCpltCallback  16  static
ethernetif.c:413:7:ethernetif_init  48  static
ethernetif.c:461:7:sys_jiffies  0  static
ethernetif.c:472:7:sys_now  0  static

von 23456789765435678987654356789 (Gast)


Lesenswert?

die guten teuren IDE's werten das nch aus und zeigen es dann an..
vieleicht gibts ja auch ein eclipse plugin für sowas ...

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


Lesenswert?

Omega G. schrieb:
> Axel S. schrieb:
>>> int array_A[100];    // Das landet nicht auf dem Stack.
>>>                      // Static kommt wohl von Compiler hinzu.
>>
>> Hier kommt natürlich kein static hinzu.
>>
>>> static array_B[100]; // Das landet nicht auf dem Stack.
>>
>> Bei globalen Variablen ist static bedeutungslos.
>
> Da muss ich dir teilweise wiedersprechen. Static ist bei globalen
> Variablen nicht bedeutungslos, hat aber eine ganz andere Bedeutung als
> in Funktionen.

Stimmt. Ich war da in Gedanken immer noch beim Einfluß der Attribute auf 
die Art, wie der Compiler die Variable anlegt. Darauf hat das static 
keinen Einfluß, wohl aber auf die Sichtbarkeit.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

martin schrieb:
> Ist es richtig, dass alles was in einer geschweiften Klammer

Gemeint ist der Body einer Funktion?

> als Variable definiert ist, nicht als static declariert und nicht
> dynamisch angelegt ist, auf dem Stack landet, daneben noch Ein- und
> Rücksprungadressen der Funktionen.

Im C-Standard gibt es keinen "Stack"; wie das umgesetzt wird, hängt vom 
Compiler / ABI ab.  Im einfachsten Falle würde ein Compiler alles auf 
den Stack klatschen; bis auf zu übergebende Argumente von Funktionen — 
da schreibt das ABI vor wie und wo die zu übergeben sind.

> int main(void)
> {
>   // Das landet vermutlich auf dem Stack. Bin mir aber nicht sicher.
>   int array_C[100];

Prinzipiell ja.  Falls der Compiler Details von "funktion" kennt (wenn 
"funktion" z.B. nur das erste Element braucht / verändert) ist 
vorstellbar, dass kein Array angelegt wird sondern nur das erste Element 
in einem Prozessorregister.

>   // Das landet vermutlich "nicht" auf dem Stack wegen static.
>   static int array_D[100];

Wird im Static Sorage angelegt.  Aber wie immer gilt die "as-if" Regel: 
Die vom Compiler generierte Code muss sich verhalten wie die vom 
Standard beschriebene abstrakte Maschine.  Wie ein Compiler das konkret 
erreicht ist seine Sache, wobei natürlich Standard und ABI einzuhaltende 
Rahmenbedingungen sind.

>   for(;;) // Endlosschleife. Wird vom Programm nicht verlassen
>   {
>     // z Wird wohl permanent auf den Stack rauf und runtergenommen.
>     int z = 2;

Hängt auch von der C(++) Implementation ab.  Der Wert ist simpel genug, 
einfach eine "2" in das Register zu laden, in dem das 5. Argument von 
"funktion" übergeben wird.  Falls dieses auf dem Stack zu übergeben ist, 
wird der Wert "2" auf den Stack gelegt, aber dafür brauch "z" nicht im 
Aufrufer auf dem Stack zu liegen.

Ein ABI könnte sogar festlegen, dass die Adresse von z übergeben wird, 
was dann dem Handling von großen Strukturen ähneln würde.  Der Calle 
würde über die Adresse auf z zugreifen, und wenn z im Calle verändert 
würde, lokal eine Kopie von z ziehen.

Wie / wann der Stack manipuliert wird ist Detail der Implementation und 
lässt sich anhand von Quellcode, Compiler und Architektur lediglich 
erahnen.

Falls in der Schleife mehr Stack gebraucht wird, muss der nicht 
unbedingt in jedem Durchlauf angepasst werden.  Evtl. kann die Anpassung 
auch aus der Schleife rausgezogen werden bis an den Anfang der Funktion, 
wo die Anpassung dann einmal im Prolog erfolgt und falls nötig in den 
Epilogen entsprechend rückabgewickelt wird.  Da main nie verlassen wird, 
braucht es keine Epiloge.

>     // Die unterschiedlichen Übergabemöglichkeiten sind nur Spielerei.
>     // Sollten aber wohl zulässig sein.
>     // Es wird nur die Referenz auf die Arrays übergeben.
>     // z wird kopiert
>     funktion(array_A, array_B, &array_C[0], &array_D[0], z);
>
> [...]
>   // a, b, p_c, d, y kommen auf den Stack
>   // Beim Verlassen der Funktion werden sie vom Stack genommen.

Wo und wie sie übergeben werden ist wie gesagt Sache des ABI.  Und wer 
für's Aufräumen des Stack verantwortlich ist kann auch von der 
Implementation abgängen: Caller oder Callee.

Es kann z.B. sein, dass der Callee nicht weiß, wie viel Stack der caller 
belegt hat, und kann ihn deshalb auch nicht freigeben.  Das ist z.B. mit 
varargs der Fall.

Auch die Übergabe von Strukturen wird im ABI geregelt. Kleine werden 
i.d.R. kopiert und in Registern übergeben, große oder solche mit Größe 0 
werden auf dem Stack angelegt und deren Adresse übergeben.  Wird eine 
Struktur zurückgegeben, dann in Registern oder der Caller übergibt die 
Adresse des Bereicht als zusätzliches, implizites Adressargument.

Strukturen per Wert zu übergeben erfordert manchmal das Kopieren der 
Struktur, und auch hier bestimmt die Implementation oder das ABI, wer 
kopiert: Caller oder Callee.  Das Kopieren dem Callee zu überlassen hat 
den Vorteil, dass, wenn keine Kopie notwendig ist (z.B. wenn nur lesend 
auf die Struktur zugegriffen wird) das Kopieren wegoptimiert werden 
kann.

Am besten nimmst du dir einen Compiler für deine bevorzugte Architektur 
und schaust an, wie der kleine Beispiele übersetzt und wo er was wie 
anlegt und übergibt.  Den erzeugten Code zu studieren bringt da oft mehr 
Klarheit als mit einem Debugger oder Simulator durchzunudeln.

von Christian J. (Gast)


Lesenswert?

Hallo,

ich kleb mich mal hier drunter. Habe mir das alles durchgelesen aber 
wirklich schlauer bin ich auch nicht. Ich habe das gleiche Thema für den 
STM32F103 mit dem GCC. Wie gross den Stack? Ok, Programm läuft aber die 
0x100 standen da auch schon vorher drin.

Wi erzeugt der Compiler zb ein 10.000 Elemente Array was ich lokal in 
einer Routine habe? Das geht durchaus. 18,5 kb habe ich noch frei,.

Die einzige Ausgabe, die ich kriege ist die hier von EmBitz:

bin\Release\f103.map|1|Program size (bytes):   42076|
||Data size    (bytes):     248|
||BSS size     (bytes):    1664|
||             ----------------|
||Total size   (bytes):   43988   (R/W Memory: 1912)|
|||
||=== Build finished: 0 errors, 0 warnings (0 minutes, 4 seconds) ===|

von Rufus Τ. F. (rufus) Benutzerseite


Lesenswert?

Christian J. schrieb:
> Wi erzeugt der Compiler zb ein 10.000 Elemente Array was ich lokal in
> einer Routine habe?

Wenn Du es innerhalb Deiner Funktion deklarierst, und nicht als 
static, dann landet es mit sehr hoher Wahrscheinlichkeit auf dem Stack 
(auch wenn Johann sehr ausführlich die Möglichkeiten beschrieben hat, 
unter denen es das möglicherweise nicht tun wird). Geh' einfach mal 
davon aus, daß es das tut; schnapp Dir Deinen Debugger und sieh Dir den 
Stackpointer unmittelbar vor Aufruf der Funktion an, steppe in die 
Funktion und sieh Dir jetzt den Stackpointer an.

von Christian J. (Gast)


Lesenswert?

Rufus Τ. F. schrieb:
> auch wenn Johann sehr ausführlich die Möglichkeiten beschrieben hat,

Ja, weiss ich. Heisst unterm Strich aber: Es kann alles passieren :-(
Gucken wie ich den Stackpointer reinkriege, liegt im Registersatz 
irgendwo "versteckt"

von Nop (Gast)


Lesenswert?

Christian J. schrieb:

> Die einzige Ausgabe, die ich kriege ist die hier von EmBitz:

Stackverbrauch steht da nicht drin, aber gcc kann mit -fstack-usage 
überredet werden, den Stackverbrauch für jede Funktion anzugeben. Dann 
kannste Dir anhand des Callpath ja überlegen, wie groß der 
Stackverbrauch ungefähr ist.

Muß man halt noch einen konstanten weiteren Stackverbrauch pro Aufruf 
dazuaddieren, das hängt aber von der Architektur ab.

von Noch einer (Gast)


Lesenswert?

> dann landet es mit sehr hoher Wahrscheinlichkeit auf dem Stack

Bei diesen zusammengestrichenen Beispielen merkt der Compiler, das Array 
wird nicht benutzt und wirft es mit sehr hoher Wahrscheinlichkeit 
komplett raus. :-)

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.