Forum: Mikrocontroller und Digitale Elektronik Wiederholende Initialisierung von Variablen - Speicherplatz


von Benny (Gast)


Lesenswert?

Hallo zusammen,

ich hätte eine Frage zur C-Programmierung:
1
while(1)
2
{
3
  while(1)
4
  {
5
    uint8_t i = 0;
6
  }
7
}

Macht das erneute Initialisieren Probleme mit dem Speicherplatz? Sollte 
ich hier dann lieber 'static' verwenden oder muss ich 'i' zwingend 
außerhalb der inneren while deklarieren?

Danke und Grüße,
Benny

von Dr. Sommer (Gast)


Lesenswert?

Benny schrieb:
> Macht das erneute Initialisieren Probleme mit dem Speicherplatz?
Nein.

Benny schrieb:
> Sollte ich hier dann lieber 'static' verwenden
Auf keinen Fall.

Benny schrieb:
> oder muss ich 'i' zwingend außerhalb der inneren while deklarieren?
Auch nicht.

Der Compiler ist schlau genug die Allokierung tatsächlich nur genau 1x, 
am Anfang der Funktion, durchzuführen. Halte lieber den Code 
übersichtlich, und überlass dem Compiler solche Details.

von Benny (Gast)


Lesenswert?

Super, danke für die Antwort!

von Nop (Gast)


Lesenswert?

Benny schrieb:
> Macht das erneute Initialisieren Probleme mit dem Speicherplatz? Sollte
> ich hier dann lieber 'static' verwenden oder muss ich 'i' zwingend
> außerhalb der inneren while deklarieren?

Die Variable wird nur einmal angelegt, wahlweise auf dem Stack oder als 
Belegung eines CPU-Registers. Es ist kein Äquivalent zu einem 
wiederholten malloc.

"static" würde die Variable persistent über die Funktionsraufrufe der 
betreffenden FUnktion hinweg erhalten, wenn Du das brauchst. Dann wird 
die Variable nicht auf dem Stack angelegt, sondern im Bereich der 
globalen Daten (aber nur innerhalb der Funktion benutzbar), also im 
BSS-Segment.

Es ist eine Frage des Programmierstils, ob man seine lokalen Variablen 
im jeweiligen Block anlegt oder im Funktionskopf. Man sieht beide 
Herangehensweisen, je nach Programmierer.

Vorteil beim Anlegen im jeweiligen Block: es ist klar, daß diese 
Variable nur dort Gültigkeit hat, was speziell bei längeren Funktionen 
übersichtlicher ist. Außerdem erlaubt es dem Compiler mehr Optimierung.

Vorteil beim Anlegen im Funktionskopf: Man sieht auf Anhieb, wieviel 
Stack die Funktion verbraucht.

Interessanter wird folgender Fall:
1
void foo(int bar)
2
{
3
  if (bar)
4
  {
5
    char a[20];
6
    ...
7
  } else
8
  {
9
    char b[20];
10
    ...
11
  }
12
}

Hier sollte der Compiler schlau genug sein, um zu sehen, daß er auf dem 
Stack nur einmal Platz für das Array allozieren muß, weil der Scope von 
a und b sich nicht überschneiden. Ob er das aber auch tut, ist die 
andere Frage.

von Yalu X. (yalu) (Moderator)


Lesenswert?

Wie Dr. Sommer schon geschrieben hat, belegt die Variable nur einmal
Platz, entweder auf dem Stack oder in einem Prozessorregister.
Spätestens beim Verlassen der aktuellen Funktion wird der Speicherplatz
bzw. das Register wieder freigegeben. Die Initialisierung wird aber
i.Allg. in jedem Schleifendurchlauf ausgeführt, es sei denn, der
Compiler erkennt, dass der Variablenwert innerhalb der Schleife nicht
verändert wird.

Bei der Deklaration als static belegt die Variable immer Speicherplatz
(üblicherweise im .data- oder .bss-Segment), auch schon vor dem Aufruf
und nach dem Verlassen der aktuellen Funktion. Dafür wird die
Initialisierung nur ein einziges Mal, nämlich beim Programmstart
ausgeführt. Aus diesen Gründen behält die Variable auch zwischen zwei
Funktionsaufrufen ihren Wert.

von Dr. Sommer (Gast)


Lesenswert?

Nop schrieb:
> Vorteil beim Anlegen im Funktionskopf: Man sieht auf Anhieb, wieviel
> Stack die Funktion verbraucht.
Bei modernen Compilern eher nicht; da viele Variablen gar nicht auf dem 
Stack sondern nur in Registern landen, und es ggf. noch Padding-Bytes 
gibt, kann man da nicht viel ablesen. Viel besser sieht man den 
Stack-Verbrauch im Assembler-Listing. Da gibt es nämlich typischerweise 
am Anfang der Funktion eine einzelne Stack-Frame-Allokierung. Beispiel 
(ARMv7M):
1
00000000 <test>:
2
   0:  b09a        sub  sp, #104  ; 0x68
So werden auf einen Rutsch 104 Bytes allokiert. Natürlich muss man ggf. 
noch per push gebrauchten Speicher dazu addieren.

Nop schrieb:
> Ob er das aber auch tut, ist die andere Frage.
Die meisten Compiler machen das grundsätzlich immer so und nie 
anders, außer bei lokalen Arrays mit variabler Länge (VLA). Im 
Zweifelsfall hilft wie immer die Disassembly.

von Wilhelm M. (wimalopaan)


Angehängte Dateien:

Lesenswert?

Wer solche Fragen hat, darf auch einfach mal den Compiler-Explorer zu 
Rate ziehen (s.a. Anhang):

https://godbolt.org

(Wobei dort z.B. keine neuerer AVR-Compiler konfiguriert ist. Aber es 
ist kein Problem, sich das Teil lokal zu installieren und dann eine 
andere Compilerversion anzugeben, z.B. avr-gcc >= 7.0).

von Nop (Gast)


Lesenswert?

Dr. Sommer schrieb:
> Viel besser sieht man den
> Stack-Verbrauch im Assembler-Listing.

Beim GCC kann man das sogar noch einfacher haben, indem man beim 
Compilieren die Option -fstack-usage mitgibt. Dann wirft er zu jedem 
compilierten C-File auch noch ein .su-File aus, wo man den 
Stackverbrauch der einzelnen Funktionen sieht.

Leider nur den der Einzelfunktionen und nicht etwa den Calltree, so wie 
es der Keil-Compiler macht. Aber irgendwo hab ich im Netz mal ein 
Perlscript gesehen, was das besorgt.

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.