www.mikrocontroller.net

Forum: Compiler & IDEs Zulässige Verschachtelungstiefe?


Autor: Martin Lutz (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Hallo,

Programmiere einen Tiny261 mit GCC. Wie tief darf die 
Verschachtelungstiefe durch den Aufruf von Unterprogrammen eigentlich 
sein und wie seh' ich, falls es dadurch ein Problem gibt?

Danke! Martin

Autor: Rufus Τ. Firefly (rufus) (Moderator) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Ein paar Punkte, die Dir dabei helfen könn(t)en, es selbst 
herauszufinden:

- Wie groß ist der für den Stack verfügbare Speicher?
- Wieviel Stack wird für jeden "Unterprogramm"-Aufruf verbraucht?
- Wieviel Stack belegen "Unterprogramme", wenn sie automatische 
Variablen verwenden?

Autor: Martin Lutz (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Hallo, danke für die Antwort.
Dann muss ich also ausprobieren, d.h. das Programm im Simulator laufen 
lassen und den Stack beobachten? Gibt es keine andere Möglichkeiten; 
könnte dies nicht der Compiler checken o.ä.?

Danke! Martin

Autor: Karl Heinz (kbuchegg) (Moderator)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Martin Lutz wrote:
> Hallo, danke für die Antwort.
> Dann muss ich also ausprobieren, d.h. das Programm im Simulator laufen
> lassen und den Stack beobachten?

Ja, ist eine Möglichkeit

> Gibt es keine andere Möglichkeiten;

Man könnte beim Programmstart den Speicher mal mit einem bestimmten
Byte fluten und nach einiger Zeit nachsehen, wieviel von diesem
'Muster' noch im Speicher vorhanden ist. Dort wo das Muster noch
existiert, ist der Speicher offenbar nicht beschrieben worden
und daher hat sich der Stack auch nicht bis dorthin ausgedehnt.

> könnte dies nicht der Compiler checken o.ä.?

Wie soll er das tun?
In dem Moment in dem ein paar Funktionsaufrufe von irgendwelchen
Bedingungen abhängen und diese wiederrum von irgendwelchen
Zuständen irgendwelcher I/O Pins, hängt der Speicherverbrauch
davon ab, was sich extern an den Anschlüssen tut. Nur: Wie
soll der Compiler wissen, was sich extern an den Anschlüssen
tut?

Das einzige was der Compiler machen kann: Bei jedem Funktions-
aufruf am Anfang der Funktion einen Check einbauen, so dass
die Funktion selbst prüft, ob der Stackpointer in Kollision
mit dem Heap kommt. Der Nachteil: Das verbaucht Resourcen,
sowohl Laufzeit als auch Programm-Speicher. Beides ist
auf einem µC sowieso meistens knapp.

Autor: Gabriel Wegscheider (gagosoft)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Verschachtelte Aufrufe KANN der Compiler nicht prüfen, da dies ja kein 
sttischer Wert ist sondern dynamisch - währent der Ausführung - 
passiert. Ein Compiler kann nicht in die Kristallkugel schauen, wie oft 
sich ein verschaltelter Aufruf verschachtelt. Also selbst ist der µC 
Programierer, und sehen, wie viel Stack brauchst Du pro Aufruf, wieviele 
lokale Variablen benötigst Du dabei. Dem ganzen ziehst Du den Platz der 
globalen Variablen ab (die liegen an den unteren Adressen des RAM). 
Daraus kannst Du Dir die benötigte Speichergrösse ableiten. Achtung, am 
besten siehst Du Dir des ASM-Code an, der Controller pusht auch gerne 
Register auf den Stack, bevor er die Subroutine ausführt. Jedenfalls 
kommt die Returnadresse auf den Stack.
avr-objdump -g myProject.elf
 liefert sehr brauchbare Angaben.

Viel Spass beim Zählen ist eine eher langweilige Aufgabe. Kann der 
Simulator den Du verwendest Stackframes und Variablen im Speicher 
anzeigen?

Autor: Peter Dannegger (peda)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Gabriel Wegscheider wrote:
> Verschachtelte Aufrufe KANN der Compiler nicht prüfen, da dies ja kein
> sttischer Wert ist sondern dynamisch - währent der Ausführung -
> passiert. Ein Compiler kann nicht in die Kristallkugel schauen, wie oft
> sich ein verschaltelter Aufruf verschachtelt.

Das könnte der Compiler sehr wohl tun, wenn man davon ausgeht, daß alle 
Bedingungen wahr sind und keine Rekursion erfolgt.

Der Keil C51 macht das auch, um den SRAM überlagern zu können.

Rekursionen sollte man auf nem MC generell meiden, da sie exzessiven 
SRAM- und CPU-Zeit-Verbrauch bewirken.


Ich schreibe gerne viele kleine Funktionen (ab 5-Zeiler) und hatte noch 
nie Probleme wegen der Schachtelungstiefe.

Vielleicht beim ATtiny13 könnte es knapp werden (64 Byte SRAM), aber da 
geht eher der Flash aus (512 Words).


Peter

Autor: Εrnst B✶ (ernst)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Irgendwo hier flog mal ein Stück Code rum, das den Stackverbrauch 
"nachmessen" kann:

in einer der .init-Sections wird der Speicher mit nem Wert 
überschrieben, und später kann man nachzählen wieviel vom Speicher noch 
diesen Wert hat, also wie tief der Stack in der Zwischenzeit gewachsen 
ist.


mem-check.h:
#ifndef _MEM_CHECK_H_
#define _MEM_CHECK_H_

extern unsigned short get_mem_unused (void);

#endif  /* _MEM_CHECK_H_ */

mem-check.c:
#include <avr/io.h>  // RAMEND
#include "mem-check.h"

// Mask to init SRAM and check against
#define MASK 0xaa

// From linker script
extern unsigned char __heap_start;

unsigned short
get_mem_unused (void)
{
   unsigned short unused = 0;
   unsigned char *p = &__heap_start;

   do
   {
      if (*p++ != MASK)
         break;

      unused++;
   } while (p <= (unsigned char*) RAMEND);

      return unused;
}

/* !!! never call this function !!! */
void __attribute__ ((naked, section (".init8")))
__init8_mem (void)
{
   __asm volatile (
      "ldi r30, lo8 (__heap_start)"  "\n\t"
      "ldi r31, hi8 (__heap_start)"  "\n\t"
      "ldi r24, %0"                  "\n\t"
      "ldi r25, hi8 (%1)"            "\n"
      "0:"                           "\n\t"
      "st  Z+,  r24"                 "\n\t"
      "cpi r30, lo8 (%1)"            "\n\t"
      "cpc r31, r25"                 "\n\t"
      "brlo 0b"
         :
         : "i" (MASK), "i" (RAMEND+1)
   );
}

Autor: Matthias (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Bei einem so kleinen Controller würde ich keine "lokalen" Variablen 
empfehlen. Dann gibt einem der Compiler gleich eine Übersicht, über
den verbrauch an RAM (glaub .bss ?). Ausserdem meckert er in jedem Fall, 
wenn  der Speicher ausgeht.

Das kann man dann nutzen, um die Größe des restlichen RAMs zu ermitteln:

Array anlegen und die Größe solange erhöhen bis eine Fehlermeldung 
kommt.
Die Größe, bevor die Fehlermeldung kommt, ist dann der noch freie 
Speicher.

Dann hängt es letztlich von der Anzahl der Funktionen ab, die 
verschachtelt aufgerufen werden.
Ein AVR braucht 2 bzw. 3 Byte pro Funktionsaufruf, um die 
Rückspringadresse zu sichern. Der Compiler wird dann wahrscheinlich noch 
ein paar Register sichern, die innerhalb der Funktion benutzt werden.

Hat man nur globale Variablen, sollte das hoffentlich recht wenig sein.
Kann man allerdings im Assemblercode prüfen.

Bei kleinen Controllern kann man sich die Verschachtelung meistens an 
einer Hand abzählen. Dazu rechnet man dann noch einen ISR Aufruf dazu.

Weiss jeman, wie man die Anzahl der vom Compiler gesicherten Register 
bei einem Funktionsaufruf abschätzen / bestimmen kann? (ausser im 
Assemblercode zu schauen)?

Autor: Gabriel Wegscheider (gagosoft)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Peter Dannegger wrote:
>Das könnte der Compiler sehr wohl tun, wenn man davon ausgeht, daß alle
>Bedingungen wahr sind und keine Rekursion erfolgt.
>
>Der Keil C51 macht das auch, um den SRAM überlagern zu können.
>
>Rekursionen sollte man auf nem MC generell meiden, da sie exzessiven
>SRAM- und CPU-Zeit-Verbrauch bewirken.
Das ist aber eigentlich nicht die Aufgabe des Compilers, schön wenn 
dieser Compiler das kann.

Matthias wrote:
> Bei einem so kleinen Controller würde ich keine "lokalen" Variablen
> empfehlen. Dann gibt einem der Compiler gleich eine Übersicht, über
> den verbrauch an RAM (glaub .bss ?). Ausserdem meckert er in jedem Fall,
> wenn  der Speicher ausgeht.
Gerade bei einem kleinen Controller sind lokale Variblan sinnvoll, das 
hier der Speicher mehrfach genutzt wird. Die lokalen Variablen werden 
auf dem Stack angelegt und der verwendete Speicher ist dannach wieder 
frei. Globale Variablen hingegen belegen den Speicher von Anfang an und 
schliessen die Möglichkeit aus, einen Speicherbereich merhrfach zu 
verwenden.

> Das kann man dann nutzen, um die Größe des restlichen RAMs zu ermitteln:
>
> Array anlegen und die Größe solange erhöhen bis eine Fehlermeldung
> kommt.
> Die Größe, bevor die Fehlermeldung kommt, ist dann der noch freie
> Speicher.
>
> Dann hängt es letztlich von der Anzahl der Funktionen ab, die
> verschachtelt aufgerufen werden.
> Ein AVR braucht 2 bzw. 3 Byte pro Funktionsaufruf, um die
> Rückspringadresse zu sichern. Der Compiler wird dann wahrscheinlich noch
> ein paar Register sichern, die innerhalb der Funktion benutzt werden.
>
Und mit den globalen Variablen hast Du noch lange nicht den 
Speicherverbrauch abgedecht. Bei jedem Funktionsaufruf werden die 
Parameter, die Rücksprungadresse und "ganz nach Lust und Laune" des 
Compilers Register abgelegt (push)

> Hat man nur globale Variablen, sollte das hoffentlich recht wenig sein.
> Kann man allerdings im Assemblercode prüfen.
>
> Bei kleinen Controllern kann man sich die Verschachtelung meistens an
> einer Hand abzählen. Dazu rechnet man dann noch einen ISR Aufruf dazu.
>
> Weiss jeman, wie man die Anzahl der vom Compiler gesicherten Register
> bei einem Funktionsaufruf abschätzen / bestimmen kann? (ausser im
> Assemblercode zu schauen)?
Nein kann man nicht einheitlich sagen. Ich hab in einem kleine Projekt 
gerade Stackframes-Sizes zwischen 4 und 18 Bytes gemessen.
Die Grösse der Stackframes ist sehr unterschiedlich, lässt jedoch für 
jede Funktion auch ausrechnen.
avr-objdump -g myProject.elf
liefert alle Daten, "ein paar regexp mit etwas logik" darüberlaufen und 
man kann einen Report generieren:
StackFrame: 'global' from 0xFFFFFFFF to 0x0 PushCount 0 FrameSize 8388719
    Var   'PID_Param.Kp   ' of 'int16_t             ' @ 0x800068
    Var   'PID_Param.Ki   ' of 'int16_t             ' @ 0x80006A
    Var   'PID_Param.Kd   ' of 'int16_t             ' @ 0x80006C
    Var   'PID_Param.Ta   ' of 'int16_t             ' @ 0x80006E
    Var   'DEBUG          ' of 'int16_t             ' @ 0x800064
    Var   'uDEBUG         ' of 'uint16_t            ' @ 0x800066
    Var   'sum            ' of 'int16_t             ' @ 0x800062
    Var   'last_error     ' of 'int16_t             ' @ 0x800060
StackFrame: '__vectors' from 0x0 to 0x26 PushCount 0 FrameSize 0
StackFrame: '__bad_interrupt' from 0x5A to 0x5C PushCount 0 FrameSize 0
StackFrame: 'main' from 0x5C to 0xB8 PushCount 0 FrameSize 8
    Var   'result         ' of 'unsigned_int        ' @ 0x3
    Var   'ref            ' of 'unsigned_int        ' @ 0x5
    Var   'sens           ' of 'unsigned_int        ' @ 0x7
    Var   'x              ' of 'unsigned_int        ' @ 0x1
StackFrame: 'PIDcontroller_init' from 0xB8 to 0x102 PushCount 2 FrameSize 4
    Var   'reference      ' of 'int16_t             ' @ 0x1
    Var   'process        ' of 'int16_t             ' @ 0x3
StackFrame: 'multULimit' from 0x102 to 0x164 PushCount 2 FrameSize 6
    Var   'a              ' of 'uint16_t            ' @ 0x3
    Var   'b              ' of 'uint16_t            ' @ 0x5
    Var   'result         ' of 'uint16_t            ' @ 0x1
StackFrame: 'multSULimit' from 0x164 to 0x244 PushCount 6 FrameSize 8
    Var   'a              ' of 'int16_t             ' @ 0x5
    Var   'b              ' of 'uint16_t            ' @ 0x7
    Var   'result         ' of 'int32_t             ' @ 0x1
StackFrame: 'PIDcontrollerUnsigned_schedule' from 0x244 to 0x412 PushCount 2 FrameSize 18
    Var   'reference      ' of 'int16_t             ' @ 0xF
    Var   'process        ' of 'int16_t             ' @ 0x11
    Var   'result         ' of 'int32_t             ' @ 0x1
    Var   'DPart          ' of 'uint16_t            ' @ 0x5
    Var   'IPart          ' of 'uint16_t            ' @ 0x7
    Var   'tmp            ' of 'uint16_t            ' @ 0x9
    Var   'PPart          ' of 'uint16_t            ' @ 0xB
    Var   'error          ' of 'int16_t             ' @ 0xD

Autor: Gabriel Wegscheider (gagosoft)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Sobald dann inline asm drinnen ist, macht mein Analyscode derzeit eine 
Bauchlandung.... Da liefert nähmlich avr-objdump keine sinnvollen 
Ergebnisse mehr   :(

Autor: Andreas K. (a-k)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Matthias wrote:

> Bei einem so kleinen Controller würde ich keine "lokalen" Variablen
> empfehlen.

Im Gegenteil. Ein AVR hat recht viele Register und die meisten 
Funktionen kommen deshalb ausschliesslich mit Registern aus, müssen also 
dafür keinen Speicher adressieren. Das kommt dem Code sehr zugute.

Was hingegen tatsählich sinnvoll sein kann: nicht-skalare Variablen 
(arrays, structs) und Variablen deren Adresse verwendet wird als static 
zu deklarieren. Denn solche Variablen landen sonst unweigerlich auf dem 
Stack, und AVR kann damit zwar besser umgehen als etliche anderen 
8-Bitter, aber immer noch schlechter als mit direkt adressierten. 
Ausserdem ist die Verwaltung eines Stackframe beim GCC u.U. recht 
aufwendig, wird aber nur nötig wenn solche Variablen überhaupt 
existieren.

> Dann gibt einem der Compiler gleich eine Übersicht,

Inwieweit das ein Vorteil ist hängt auch vom Compiler ab. Auf 
Microcontroller wie 8051 und PIC spezialisierte Compiler pflegen das 
Program draufhin zu untersuchen, welche Variablen übereinander gelegt 
werden können. GCC tut das aufgrund völlig anderer Herkunft nicht. Daher 
sorgen statisch angelegte Variablen beim GCC für deutlich höheren 
RAM-Verbrauch als lokale Variablen.

Zudem leidet der Code massiv, wenn skalare Daten statisch angelegt 
werden, so dass nebem dem RAM auch noch das ROM knapp werden kann.

Autor: Andreas K. (a-k)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Gabriel Wegscheider wrote:

> liefert alle Daten, "ein paar regexp mit etwas logik" darüberlaufen und
> man kann einen Report generieren:

Wobei man da ein bischen was draufrechnen muss. Seht dort 0, kommen noch 
2-3 Bytes für die Return-Adresse dazu. Bei > 0 muss man darüber hinaus 2 
Bytes für den Frame-Pointer hinzurechnen.

Autor: Peter Dannegger (peda)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Hier mal eine Funktion, mit der man den Stackverbrauch zur Laufzeit 
ermitteln kann.
Funktioniert allerdings nur, wenn kein malloc benutzt wird.

#define FREE_MARK 0x77

extern u8 __bss_end;                    // lowest stack address

extern u8 __stack;                      // highest stack address


u16 stack_size( void )                  // available stack
{
  return (u16)&__stack - (u16)&__bss_end + 1;
}


u16 stack_free( void )                  // unused stack after last call
{
  u8 flag = 1;
  u16 i, free = 0;
  u8 * mp = &__bss_end;

  for( i = SP - (u16)&__bss_end + 1; i; i--){
    if( *mp != FREE_MARK )
      flag = 0;
    free += flag;
    *mp++ = FREE_MARK;
  }
  return free;
}



Peter

Autor: Gabriel Wegscheider (gagosoft)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Andreas Kaiser wrote:
>> liefert alle Daten, "ein paar regexp mit etwas logik" darüberlaufen und
>> man kann einen Report generieren:
> Wobei man da ein bischen was draufrechnen muss. Seht dort 0, kommen noch
> 2-3 Bytes für die Return-Adresse dazu. Bei > 0 muss man darüber hinaus 2
> Bytes für den Frame-Pointer hinzurechnen.
Hier nicht!
Es steht 0 dort, wenn der Stackframe (oder pseudo-Stackframe) 0 ist. die 
Sektion __vectors ist die Jumptable am Anfang des Programmspeichers, die 
hat KEINEN Frame, daher 0 Byte Framesize. "global" ist auch kein echter 
Frame, hab ihm aber in der Datenstruktur Frames reingepackt. Daher hat 
"global" auch eine Size von -1.
Die Returnadresse, Returnparameter und Funktiuonsparameter hab ich bei 
diesen Berechnungen bereits berücksichtigt. Der PushCount wird aus 
"avr-objdump -d" extrahiert.
Soweit ich das im Simulator verifiziert habe stimmen diese Frameangaben 
(sonst hätt ich sie ja korrigiert... :)  )
... das mit "ein paar regexp und etwas logik" wahr etwas salopp 
formuliert, das Parsen der avr-objdumps entspricht ~ 300 LOC (CPP mit 
vielen RegExp). Die Strukturen noch nicht eingerechnet.

Autor: Εrnst B✶ (ernst)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
@Peter:

Da gefällt mir die Version die ich weiter oben gepostet hab aber besser.
Die kann man einfach irgendwo, z.B. in der main-schleife, aufrufen und 
das Ergebnis ausgeben, und hat dort den Peak-Wert des Stackverbrauchs, 
inkl. IRQs die seit Programmstart aufgerufen wurden.

Autor: Peter Dannegger (peda)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Ernst Bachmann wrote:
> @Peter:
>
> Da gefällt mir die Version die ich weiter oben gepostet hab aber besser.
> Die kann man einfach irgendwo, z.B. in der main-schleife, aufrufen und
> das Ergebnis ausgeben, und hat dort den Peak-Wert des Stackverbrauchs,
> inkl. IRQs die seit Programmstart aufgerufen wurden.

Ich wüßte jetzt nicht, wo meine Funktion Beschränkungen bezüglich der 
Aufrufbarkeit hätte.


Man ruft sie einmal am Anfang des main auf, damit sie den ungenutzten 
Bereich mit dem Muster füllt.
Und dann an beliebiger anderer Stelle, um den Verbrauch, seit dem 
letzten Aufruf zu ermitteln.

Das ist ganz praktisch. Man kann damit eine Sequenz klammern, die man 
als Speicherfresser in Verdacht hat und prüfen.

Man kann sie also beliebig und mehrmals aufrufen.


Ich versuche möglichst kein Assembler zu nehmen, wenns auch in C geht.


Peter

Autor: Εrnst B✶ (ernst)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
@Peter:

Ups, stimmt...
Hab ganz übersehen das die Schleife sozusagen den freien Speicher für 
den nächsten Aufruf vorbereitet, und bin wg. dem Schleifenende bei "SP" 
davon ausgegangen dass er nur den aktuellen Stack-Füllstand 
betrachtet...

Nehme also alles zurück und behaupte das Gegenteil, schöne Funktion und 
spart das Herumgefummel in den .init-Sections.

Antwort schreiben

Die Angabe einer E-Mail-Adresse ist freiwillig. Wenn Sie automatisch per E-Mail über Antworten auf Ihren Beitrag informiert werden möchten, melden Sie sich bitte an.

Wichtige Regeln - erst lesen, dann posten!

  • Groß- und Kleinschreibung verwenden
  • Längeren Sourcecode nicht im Text einfügen, sondern als Dateianhang

Formatierung (mehr Informationen...)

  • [c]C-Code[/c]
  • [avrasm]AVR-Assembler-Code[/avrasm]
  • [code]Code in anderen Sprachen, ASCII-Zeichnungen[/code]
  • [math]Formel in LaTeX-Syntax[/math]
  • [[Titel]] - Link zu Artikel
  • Verweis auf anderen Beitrag einfügen: Rechtsklick auf Beitragstitel,
    "Adresse kopieren", und in den Text einfügen




Bild automatisch verkleinern, falls nötig
Bitte das JPG-Format nur für Fotos und Scans verwenden!
Zeichnungen und Screenshots im PNG- oder
GIF-Format hochladen. Siehe Bildformate.
Hinweis: der ursprüngliche Beitrag ist mehr als 6 Monate alt.
Bitte hier nur auf die ursprüngliche Frage antworten,
für neue Fragen einen neuen Beitrag erstellen.

Mit dem Abschicken bestätigst du, die Nutzungsbedingungen anzuerkennen.