Forum: Mikrocontroller und Digitale Elektronik variable innerhalb oder außerhalb einer for-schleife unterschied der Gültigkeit


von Fabian (Gast)


Lesenswert?

guten morgen,

ich habe eine Funktion, mit einer for-schleifen
1
funktion(const struct dataStruct* data_ps)
2
{  
3
  for(uint8_t x=0; x<data_ps->length; x++)
4
  {
5
    uint8_t y = getAddress(data_ps->data[x]);
6
    if(y<NOF_END)
7
    {
8
      ... ... ...
9
    }
10
  }
11
  /* y ist hier nicht mehr gültig bzw. vorhanden */
12
  ... ... ...
13
}

die Variable y ist ja jetzt nur innerhalb der for-schleife gültig.
Möchte ich diese außerhalb auch noch benutzen, muss ich diese entweder 
neu oder außerhalb der schleife definieren.

Meine Frage ist bezüglich des speicherplatzes.
Ist dieser unterschiedlich?
1
funktion(const struct dataStruct* data_ps)
2
{
3
  uint8_t y;
4
  for(uint8_t x=0; x<data_ps->length; x++)
5
  {
6
    y = getAddress(data_ps->data[x]);
7
    if(y<NOF_END)
8
    {
9
      ... ... ...
10
    }
11
  }
12
  /* y ist hier gültig und vorhanden */
13
  ... ... ...
14
}

oder ist das letztendlich das selbe?
Aber wie ist das dann im gebauten code? Also woran liegt es, dass diese 
nicht mehr gültig ist?

von Monk (roehrmond)


Lesenswert?

Die Variable liegt in beiden Fällen im Stapelspeicher. Dieser ist bis 
zum Ende der Funktion gültig.

Bei der Gültigkeit geht es darum, welche Teile deines Quelltextes über 
den namen der Variable auf sie zugreifen können. Das ist eher eine 
Einschränkung der Programmiersprache, ganz unabhängig von den Vorgängen 
in der Hardware.

Jetzt kommt ein große ABER:

Der Compiler hat an dieser Stelle die Freiheit, ein CPU Register anstatt 
einer Variable im RAM zu benutzen. Und er hat auch die Freiheit, das 
Register am Ende des gültigen Bereiches für andere Zwecke zu verwenden. 
Du kannst davon ausgehen, dass er das auch tun wird.

: Bearbeitet durch User
von Peter D. (peda)


Lesenswert?

Fabian schrieb:
> /* y ist hier nicht mehr gültig bzw. vorhanden */

An dieser Stelle sind weder x noch y vorhanden.

Du mußt doch nur die {} Pärchen verfolgen, Dein Editor sollte die auch 
anzeigen. Was innerhalb eines {} definiert ist, bleibt auch innerhalb.

von Monk (roehrmond)


Lesenswert?

Bei einer Sache bin ich unsicher:
1
funktion()
2
{  
3
  for(...)
4
  {
5
    uint8_t y = ...    
6
  }
7
8
  uint8_t z = ...  
9
}

Darf der Compiler hier den Speicherplatz von y (auf dem Stack) nach der 
For-Schleife für z recyclen, so dass beide Variablen im RAM die gleiche 
Adresse haben?

: Bearbeitet durch User
von (prx) A. K. (prx)


Lesenswert?

Steve van de Grens schrieb:
> Darf der Compiler hier den Speicherplatz von y (auf dem Stack) nach der
> For-Schleife für z recyclen, so dass beide Variablen im RAM die gleiche
> Adresse haben?

ja

von Lothar M. (Firma: Titel) (lkmiller) (Moderator) Benutzerseite


Lesenswert?

Steve van de Grens schrieb:
> Die Variable liegt in beiden Fällen im Stapelspeicher. Dieser ist bis
> zum Ende der Funktion gültig.
Sicher? Wo ist dieses Verhalten definiert?

Mal das hier angenommen:
1
funktion(const struct dataStruct* data_ps)
2
{  
3
  for(uint8_t x=0; x<data_ps->length; x++)
4
  {
5
    uint8_t y = getAddress(data_ps->data[x]);   // y liegt auf dem Stack
6
    if(y<NOF_END)
7
    {
8
      ... ... ...
9
    }
10
  }
11
  /* y ist hier formell nicht mehr gültig bzw. vorhanden */
12
  // Frage: ist y hier immer noch auf den Stack?
13
14
  ... gut tausend Zeilen Code ... 
15
  uint8_t u = ...
16
  ... mit einigen Daklarationen dazwischen ...
17
  uint8_t v = ...
18
  ... nochmal ein paar hundert Zeilen Code ... 
19
  
20
  // Und hier: ist der alte Wert y hier immer noch auf dem Stack?
21
  // Oder wird der Speicherplatz von nachfolgend deklarierten Variablen verwendet?
22
}

: Bearbeitet durch Moderator
von Programmierer (Gast)


Lesenswert?

Steve van de Grens schrieb:
> Darf der Compiler hier den Speicherplatz von y (auf dem Stack) nach der
> For-Schleife für z recyclen

Ja. Selbst dann wenn das y außerhalb einer Schleife deklariert ist, aber 
danach nicht mehr verwendet wird. Wie schon erwähnt werden lokale 
Variablen oft nur in Registern gehalten, und die werden ständig 
"recycelt". Es sei denn, man schreibt irgendwo &y , dann muss die 
Variable auf den Stack und der Speicher muss verfügbar bleiben bis zum 
Ende des Scope.

von Monk (roehrmond)


Lesenswert?

Lothar M. schrieb:
>> Die Variable liegt in beiden Fällen im Stapelspeicher. Dieser ist bis
>> zum Ende der Funktion gültig.
> Sicher? Wo ist dieses Verhalten definiert?

Das habe ich so in der Ausbildung gelernt. Bei Eintritt in die Funktion 
wird Stack belegt und bei Austritt wir der Stack wieder frei gegeben.

Dein Beispiel unter dem Einwand deckt sich mit meiner danach folgenden 
Frage, die in Beitrag "variable innerhalb oder außerhalb einer for-schleife unterschied der Gültigkeit" 
beantwortet wurde. Das hat sich wohl zeitlich überschnitten.

> Und hier: ist der alte Wert y hier immer noch auf dem Stack?

Wahrscheinlich nicht. Der Stack an sich ist aber bis zum Ende der 
Funktion gültig.

: Bearbeitet durch User
von Lothar M. (Firma: Titel) (lkmiller) (Moderator) Benutzerseite


Lesenswert?

Steve van de Grens schrieb:
> Das habe ich so in der Ausbildung gelernt.
Ist dir der Begriff "Scope" oder Sichtbarkeit" geläufig?

http://www.coder-welten.de/einstieg/variablen-typen-und-sichtbarkeit-26.html

von Monk (roehrmond)


Lesenswert?

Programmierer schrieb:
> Es sei denn, man schreibt irgendwo &y , dann muss die
> Variable auf den Stack und der Speicher muss verfügbar
> bleiben bis zum Ende des Scope.

Wirklich? Kann man sich darauf verlassen, dass der Speicherplatz in 
diesem Fall nicht recycelt wird?

von Monk (roehrmond)


Lesenswert?

Lothar M. schrieb:
> Ist dir der Begriff "Scope" oder Sichtbarkeit" geläufig?

Ja. Die Regeln der Sprache sagen aber nur wenig darüber aus, was 
wirklich im Speicher passiert (wohl mit Absicht).

Die Frage war ja:

Fabian schrieb:
> Meine Frage ist bezüglich des Speicherplatzes. Ist dieser unterschiedlich?

: Bearbeitet durch User
von (prx) A. K. (prx)


Lesenswert?

Steve van de Grens schrieb:
> Das habe ich so in der Ausbildung gelernt. Bei Eintritt in die Funktion
> wird Stack belegt und bei Austritt wir der Stack wieder frei gegeben.

Das ist lediglich ein für reale Compiler typisches Verhalten, keine 
Spracheigenschaft.

von Oliver S. (oliverso)


Lesenswert?

Steve van de Grens schrieb:
> Die Frage war ja:
>
> Fabian schrieb:
>> Meine Frage ist bezüglich des Speicherplatzes. Ist dieser unterschiedlich?

Nein. Bei eingeschalteter Optimierung macht der Compiler daraus ziemlich 
sicher das gleiche. Und wie weiter oben ja schon angemerkt wurde, werden 
die Variablen der Schleife alle in Registern gehalten.

Oliver

von der_eine (Gast)


Lesenswert?

Lothar M. schrieb:
> // Und hier: ist der alte Wert y hier immer noch auf dem Stack?
>   // Oder wird der Speicherplatz von nachfolgend deklarierten Variablen
> verwendet?
> }

Die allgemein gültige Antwort: "das kommt drauf an" (was das Compiler 
draus macht). Höchstwahrscheinlich ist es so (der Speicherplatz wird von 
nachfolgenden Variablen verwendet), aber garantieren kann man es nicht.

Wenn y jetzt ein hochgeheimer Wert wäre (z.B. ein privater Key zur 
Verschlüsselung), dann sollte man tunlichst andere Maßnahmen ergreifen, 
um diesen sicher aus dem Speicher zu entfernen als eine andere Variable 
anzulegen, und zu hoffen, daß der Compiler damit den hochgeheimen Wert 
überschreibt.

Wenn man es wirklich wissen möchte was passiert, dann wird einem ein 
Blick in das Assembler-File nicht erspart bleiben. Aber Vorsicht: Wenn 
sich der Code Ändert, oder man an den Compiler-Optimierungen 
rumschraubt, kann das Ergebnis ganz anders sein.

Gruß

von Programmierer (Gast)


Lesenswert?

Steve van de Grens schrieb:
> Programmierer schrieb:
>
>> Es sei denn, man schreibt irgendwo &y , dann muss die
>> Variable auf den Stack und der Speicher muss verfügbar
>> bleiben bis zum Ende des Scope.
>
> Wirklich? Kann man sich darauf verlassen, dass der Speicherplatz in
> diesem Fall nicht recycelt wird?

Ja, der Sprachstandard verlangt das. Bei so einem Code:
1
int foo () {
2
  int y = 42;
3
  int* p = &y;
4
  
5
  // ... ganz viel Code mit vielen Variablen der aber y und p nicht benutzt ...
6
  
7
  return *p;
8
}

Muss 42 zurückgegeben werden, y muss bis zum Ende existieren. Wenn man 
aber nirgendwo einen Zeiger (oder in C++: eine Referenz) auf y nimmt, 
sieht es anders aus.

Am Besten ist es sich den Assembler-Code anzuschauen, das ist sehr 
lehrreich.

von Peter D. (peda)


Lesenswert?

Programmierer schrieb:
> Muss 42 zurückgegeben werden, y muss bis zum Ende existieren.

Muß nicht, der Compiler darf zu "return 42;" optimieren.
Compiler sind typisch recht faul. Sie legen Variablen erst zum spätest 
möglichen Zeitpunkt an.
"int y = 42;" bedeutet also nicht, daß y auch sofort angelegt wird.

von (prx) A. K. (prx)


Lesenswert?

Programmierer schrieb:
>> Wirklich? Kann man sich darauf verlassen, dass der Speicherplatz in
>> diesem Fall nicht recycelt wird?
>
> Ja, der Sprachstandard verlangt das. Bei so einem Code:

Entscheidend ist die "as if" Regel: Der Compiler muss sich so verhalten, 
dass das Ergebnis im Rahmen des Sprachstandards stimmt.

> Muss 42 zurückgegeben werden, y muss bis zum Ende existieren.

Bei deinem Code kann ein Compiler auch einfach auf { ... return 42; } 
entscheiden, wenn er erkennt, dass in dem "ganz vielen Code" nichts über 
y und p vorkommt.
1
void g(void);
2
int f(void)
3
{
4
        int x = 0;
5
        int *p = &x;
6
        g();
7
        return *p;
8
}

kann z.B. diesen Code ergeben:
1
f:
2
        subq    $8, %rsp
3
        call    g
4
        xorl    %eax, %eax
5
        addq    $8, %rsp
6
        ret

Man sieht, dass der Speicherplatz für x zwar existiert, aber in keiner 
Weise genutzt wird. Für p existiert überhaupt kein Speicherplatz, weil 
nicht genutzt und deshalb komplett wegoptimiert.

: Bearbeitet durch User
von Monk (roehrmond)


Lesenswert?

Programmierer schrieb:
> int foo () {
>   int y = 42;
>   int* p = &y;
>   ...
>   return *p;
> }

Aber dann zeigt der Return Wert doch in den Stack der Funktion. Der 
Stack ist außerhalb der Funktion nicht mehr gültig, also kann der 
Aufrufer mit dem Zeiger nichts zuverlässiges mehr anfangen.

Ich denke, wenn man einen Zeiger auf etwas zurück liefert, dann muss das 
im Heap liegen (also innerhalb der Funktion mit new oder malloc() 
erzeugt werden).

von (prx) A. K. (prx)


Lesenswert?

Steve van de Grens schrieb:
> Aber dann zeigt der Return Wert doch in den Stack der Funktion.

Nein. Es wird ja nicht der Pointer p zurück gegeben, sondern das, worauf 
er zeigt.

von Monk (roehrmond)


Lesenswert?

(prx) A. K. schrieb:
> Es wird ja nicht der Pointer p zurück gegeben, sondern das, worauf
> er zeigt.

Ach ja, das habe ich übersehen.

von mIstA (Gast)


Lesenswert?

der_eine schrieb:
> Wenn y jetzt ein hochgeheimer Wert wäre (z.B. ein privater
> Key zur Verschlüsselung), dann sollte man tunlichst andere
> Maßnahmen ergreifen, um diesen sicher aus dem Speicher zu
> entfernen als eine andere Variable anzulegen, und zu hoffen,
> daß der Compiler damit den hochgeheimen Wert überschreibt.


Ach ja, einfach y einen neuen Wert zuweisen wird i.A. auch nicht 
ausreichend sein; denn wenn für den Compiler erkennbar ist, daß y in 
weiterer Folge nicht mehr benötigt wird, darf er auch gleich die 
Zuweisung wegoptimieren.

von EAF (Gast)


Lesenswert?

mIstA schrieb:
> darf er auch gleich die
> Zuweisung wegoptimieren.

volatile

von (prx) A. K. (prx)


Lesenswert?

(prx) A. K. schrieb:
> Man sieht, dass der Speicherplatz für x zwar existiert

Aber nur deshalb, weil der Stack sonst im Call misaligned wäre.

: Bearbeitet durch User
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.