Forum: PC-Programmierung Frage zur while-Schleife und lokalen Variablen


von Hano G. (Gast)


Lesenswert?

Hallo zusammen,

ich hätte eine Frage zu folgendem simplen Codefragment:
1
int main()
2
{
3
    int slope = 0;
4
    while(cin >> slope){
5
        int a, b, c;
6
        a = b = c = 0;
7
        cout << "(a + b + c)*slope = " << (a+b+c)*slope << '\n';
8
    }
9
}

Solange die while-Schleife erfüllt ist, werden die Statements innerhalb 
der schleife abgearbeitet. Meine Frage ist nun, wird jedesmal auch neuer 
Speicher für die drei integer-Variablen angelegt oder wird der Speicher 
wieder freigegeben sobald die while-Schleife verlassen wird?

Gruß

von Sven B. (scummos)


Lesenswert?

Das Scope endet bei jedem Schleifendurchlauf, aber weil die Variablen 
auf dem Stack sind sind die trotzdem jedes Mal an der selben Stelle im 
Speicher.

von Peter II (Gast)


Lesenswert?

es könnte sogar sein, das die Variablen überhaupt kein Speicher 
brauchen, weil sie einfach im Prozessor Register bleibe.

du musst dich zumindest nicht darum kümmern.

von (prx) A. K. (prx)


Lesenswert?

Hano G. schrieb:
> Meine Frage ist nun, wird jedesmal auch neuer
> Speicher für die drei integer-Variablen angelegt oder wird der Speicher
> wieder freigegeben sobald die while-Schleife verlassen wird?

In C werden lokale Variablen üblicherweise am Anfang der Funktion am 
Stück reserviert und am Ende am Stück wieder freigegeben. Egal ob es 
sich dabei um Register oder RAM handelt. Was sich durch die Definition 
innerhalb des Blockes ändert ist nur die Sichtbarkeit, nicht der Platz.

Unabhängig davon, ob Variablen am Anfang einer Funktion oder innerhalb 
eines Blockes definiert werden ist keineswegs sicher, dass sie immer an 
der gleichen Stelle landen. Grad bei Registern kann sich das 
zwischendrin ändern. So können Variablen beim zweiten Schleifendurchlauf 
in anderen Registern stehen als beim ersten.

Ein relativ sicherer Weg, Variablen ineffizient zu machen, ist 
allerdings, ihre Adresse zu verwenden. Dann kann man fast sicher sein, 
dass mindestens übergangsweise langsames umständliches RAM an Stelle von 
schnellen Registern verwendet wird.

von Hano G. (Gast)


Lesenswert?

Also kann man sagen, dass mein Beispielcode kein empfehlenswerter 
Programmierstil ist?

von (prx) A. K. (prx)


Lesenswert?

Wieso? Variablen sollten dort definiert werden, wo sie benötigt werden. 
Das ist hier der Fall.

Der Stil ist ok, sofern man dem fehlenden Sinn etwas abgewinnen kann.

: Bearbeitet durch User
von Rufus Τ. F. (rufus) Benutzerseite


Lesenswert?

Naja, Dein Programm macht nicht viel, außer

"(a + b + c)*slope = 0"

auszugeben, und das solange, wie aus cin gelesen werden kann.

von (prx) A. K. (prx)


Lesenswert?

Überlass Gedanken zur Optimierung dem Compiler, solange du wesentlich 
weniger darüber weisst als er.

Wie Rufus korrekt anmerkt, ist "scope" ohnehin die einzige effektiv 
vorkommende Variable. Die anderen 3 verschwinden gänzlich im Optimizer.

: Bearbeitet durch User
von Dussel (Gast)


Lesenswert?

C++ erlaubt diese Definition, das heißt, sie ist vorgesehen und Compiler 
werden wohl entsprechend programmiert.
Auf diese Weise wird dem Compiler klar mitgeteilt, welche Variable wo 
gültig sein soll, was ich eher als vorteilhaft ansehe. Mir fällt jetzt 
kein Konstrukt ein, in dem der Compiler das nicht selber leicht 
rausfinden könnte, aber das heißt ja nicht, dass es keins gibt.

von Rufus Τ. F. (rufus) Benutzerseite


Lesenswert?

Dussel schrieb:
> C++ erlaubt diese Definition,

Würde auf die C++-Streamoperatoren und cin/cout verzichtet werden, wäre 
das sogar legitimes, wenn auch funktionsarmes C.

von Hano G. (Gast)


Lesenswert?

In Ordnung danke, meine Bedenken waren, dass irgendwann der Speicher 
vollläuft weil ja - so dachte ich zumindest - bei jedem 
Schleifendurchlauf neue Variablen angelegt werden.

Sinn hat das Codefragment natürlich keinen, aber das war auch nicht 
gewollt, ich wollte meine Frage anhand einem simplen Beispiel stellen.

von Sven B. (scummos)


Lesenswert?

Hano G. schrieb:
> In Ordnung danke, meine Bedenken waren, dass irgendwann der Speicher
> vollläuft weil ja - so dachte ich zumindest - bei jedem
> Schleifendurchlauf neue Variablen angelegt werden.
Das kann bei Stack-Variablen nur passieren, wenn du Rekursion hast.

von qwertzuiopü+ (Gast)


Lesenswert?

Hano G. schrieb:
> bei jedem
> Schleifendurchlauf neue Variablen angelegt werden.

Werden sie auch. Aber danach auch wieder gelöscht.

von (prx) A. K. (prx)


Lesenswert?

qwertzuiopü+ schrieb:
>> Schleifendurchlauf neue Variablen angelegt werden.
>
> Werden sie auch. Aber danach auch wieder gelöscht.

Oder weder noch.

Sie werden sichtbar und wieder unsichtbar. Einzig das ist von der 
Sprache her definiert.

von qwertzuiopü+ (Gast)


Lesenswert?

Und in DEM Fall werden sie höchstwahrscheinlich gar nicht erst irgendwo 
gespeichert - 0 + 0 + 0 ist immer noch 0 und geht als Konstante in die 
Multiplikation ein. Wahrscheinlich wird die ganze Multiplikation 
wegoptimiert, weil ja mit 0 multipliziert wird.

von (prx) A. K. (prx)


Lesenswert?

qwertzuiopü+ schrieb:
> Und in DEM Fall werden sie höchstwahrscheinlich gar nicht erst irgendwo
> gespeichert - 0 + 0 + 0 ist immer noch 0 und geht als Konstante in die
> Multiplikation ein. Wahrscheinlich wird die ganze Multiplikation
> wegoptimiert, weil ja mit 0 multipliziert wird.

Ja. Hatten wir aber schon. Ich denke jedoch nicht, dass der TE auf 
diesen Aspekt hinaus wollte.

von (prx) A. K. (prx)


Lesenswert?

Um die Verwirrung etwas zu steigern: Ein Compiler ist nicht 
verpflichtet, genau das was das Programm vorgibt an genau dieser Stelle 
zu tun. Er muss sich nur so verhalten, dass das alle für ihn relevanten 
Ergebnisse dazu passen.

- Wenn er also wie hier feststellt, dass a,b,c für die Katz sind, dann 
kann er sie weglassen, ohne irgendwas am Ergebnis zu ändern.

- Wenn wir das mal ignorieren und die Kiste keine Register hat, dann 
kann er den Speicher der Variablen auch am Anfang der Funktion 
allozieren und am Ende freigeben. Auch das ändert nichts am Ergebnis.

- Auch wenn die Kiste genug Register hat, ist keineswegs festgelegt, 
dass die gleiche Variable zu jedem Zeitpunkt im gleichen Register liegt.

- Eine Variable, die einen halben Meter vor der Verwendung definiert 
wird, muss allenfalls dort existieren, wo wie verwendet wird. Nicht 
davor und nicht danach.

- Der erzeugte Code könnte auch an jedem Anfang der Schleife in den 
Laden gehen, RAM kaufen, und es am Ende der Schleife wieder zurück 
geben. Da das aber grässlich ineffizient wäre und das Ergebnis auch 
nicht verbessern würde, wird er das tunlichst bleiben lassen.

Aus einem Quelltext wie diesem ergibt sich also nicht genau, zu welchem 
Zeitpunkt eine Variable "angelegt" wird. Sondern nur, wann der 
Programmierer sie verwenden kann, d.h. wann sie für ihn sichtbar ist. 
Ob, wie und wann sie tatsächlich existiert ist Sache des Compilers.

: Bearbeitet durch User
von DirkB (Gast)


Lesenswert?

Eigentlich wird am Anfang der Funktion genug Speicher reserviert, indem 
der Stackpointer um die benötigte Anzahl verschoben wird.
Das ist ein Befehl.

Auch die Freigabe am Ende erfolgt so.

von Nase (Gast)


Lesenswert?

DirkB schrieb:
> Eigentlich wird am Anfang der Funktion genug Speicher reserviert, indem
> der Stackpointer um die benötigte Anzahl verschoben wird.
> Das ist ein Befehl.
Nein, das ist nur deine Ansicht.
Möglicherweise gibts ja garkeinen Stack.

Es ist eine Möglichkeit, den Code zu implementieren.

Strenggenommen könnte der Compiler den Formatstring auch im Vorfeld zu 
"(a + b + c)*slope = 0" zusammenbauen und dann ausgeben.

von DirkB (Gast)


Lesenswert?

Nase schrieb:
> Möglicherweise gibts ja garkeinen Stack.

Dann spendiere ich am Ende der ersten Zeile noch ein "z.B."

von Rolf M. (rmagnus)


Lesenswert?

Hano G. schrieb:
> In Ordnung danke, meine Bedenken waren, dass irgendwann der Speicher
> vollläuft weil ja - so dachte ich zumindest - bei jedem
> Schleifendurchlauf neue Variablen angelegt werden.

Sieh es so: Bei jedem Schleifendurchlauf werden zu Beginn neue Variablen 
anglegt (wie das konkret funktioniert, ist Sache des Compilers), und am 
Ende wieder zerstört. In der Regel reserviert sich der Compiler den 
Speicher dafür nur einmal, da er ihn ja sowieso immer wieder braucht.
Es gibt in C++ (wie auch in C) drei Arten von Speicherbelegung:

- statisch

Eine statische Variable existiert bis zum Programmende. Das sind z.B. 
alle globalen Variablen, alle lokalen static-Variablen und alle 
static-Membervariblen.

- automatisch

Diese werden an der Stelle, wo die Definition steht, erzeugt und beim 
Verlassen des Codeblocks, in dem sie stehen, autmatisch wieder zerstört.

- dynamisch

Das sind alle Objekte, die per new oder malloc() und Konsorten erzeugt 
werden. Das sind die einzigen, bei denen man sich explizit darum kümmern 
muss, sie wieder aufzuräumen, wenn man sie nicht mehr braucht.

A. K. schrieb:
> qwertzuiopü+ schrieb:
>>> Schleifendurchlauf neue Variablen angelegt werden.
>>
>> Werden sie auch. Aber danach auch wieder gelöscht.
>
> Oder weder noch.
>
> Sie werden sichtbar und wieder unsichtbar. Einzig das ist von der
> Sprache her definiert.

Nein, das ist falsch. Es geht nicht nur um die Sichtbarkeit, sondern um 
die Existenz der Variablen. Wenn ich in einer Schleife eine Klasse 
instanziiere, wird bei jedem Schleifendurchlauf am Anfang der 
Konstruktor und beim Verlassen der Destruktor aufgerufen und nicht nur 
einmal.

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


Lesenswert?

Rolf M. schrieb:
>> Sie werden sichtbar und wieder unsichtbar. Einzig das ist von der
>> Sprache her definiert.
>
> Nein, das ist falsch. Es geht nicht nur um die Sichtbarkeit, sondern um
> die Existenz der Variablen.

Variablen, die wegoptimiert werden, sind zwar sichtbar, aber jenseits 
des Quellcodes nicht existent. Sie existieren nur im Quellcode. Ebenso 
ist der Zeitraum einer ggf. realen Existenz nicht deckungsgleich mit dem 
Zeitraum der Sichtbarkeit. Aus dem Quellcode ergibt sich also nur die 
Sichtbarkeit (und eine Schein-Existenz), keine reale Existenz.

Rolf M. schrieb:
> Wenn ich in einer Schleife eine Klasse
> instanziiere, wird bei jedem Schleifendurchlauf am Anfang der
> Konstruktor und beim Verlassen der Destruktor aufgerufen und nicht nur
> einmal.

Auch das muss nur scheinbar stattfinden, kann real verschwinden. 
Variablen der Basisdatentypen haben freilich keine 
Konstrukturen/Destruktoren und in diesem Fall nicht einmal eine mit der 
Definition verbundene Initialisierung.

Mit geht es darum, den problematischen Begriff "angelegt" zu vermeiden. 
Wann eine Variable tatsächlich auf der realen Maschine angelegt wird, 
das geht im nicht aus der Sprache hervor. Nur für die "abstract virtual 
machine" der Sprachstandards ist das klar definiert. Daraus lässt sich 
aber nicht auf das Verhalten realer Maschinen in dieser Frage 
schliessen. Das reale Programm muss sich nur so verhalten, als ob 
angelegt wären, sie müssen es aber nicht sein.

Man kann und wird C/C++ sinnvollerweise (zunächst) im Sinn der "abstract 
virtual machine" lehren. Aber wenn wie hier die Frage nach der 
tatsächlichen Speicherbelegung und dem Verhalten realer Maschinen kommt, 
verlässt man diesen Bereich.

Andernfalls suggeriert man, dass
  void f() { int a,b,c; ... }
effizienter wäre als
  void f() { ... while (x) { int a,b,c; ... } }
weil die Vars nur einmal angelegt würden.

: Bearbeitet durch User
von Rolf M. (rmagnus)


Lesenswert?

A. K. schrieb:
> Wann eine Variable tatsächlich auf der realen Maschine angelegt wird,
> das geht im nicht aus der Sprache hervor. Nur für die "abstract virtual
> machine" der Sprachstandards ist das klar definiert.

Nun gut, auf die hatte ich mich bezogen. Dass im Hintergrund durch 
Optimierung auch was anderes passieren kann, ist schon klar.

> Andernfalls suggeriert man, dass
>   void f() { int a,b,c; ... }
> effizienter wäre als
>   void f() { ... while (x) { int a,b,c; ... } }
> weil die Vars nur einmal angelegt würden.

Das würde ich nicht sagen, da die Sprache überhaupt keine Aussagen über 
Laufzeiten macht. Wenn eine Variable "anglegt" wird, heißt das ja nicht 
zwangsläufig, dass dabei ein signifikanter Aufwand entsteht - oder 
überhaupt irgendein Aufwand.

von Daniel A. (daniel-a)


Lesenswert?

Ich habe gerade eine Situation in welcher das Relevant sein könnte:
1
void func1(unsigned count){
2
  if(count>20)
3
    return;
4
  {
5
    int x[count];
6
    func2(count,x);
7
  }
8
}
Muss ich mir hier sorgen wegen einem möglichen Stackoverflow machen? 
Falls es noch for dem if zum verschieben des Stackpointers aufgrund der 
Zeile "int x[count]" kommen könnte, wäre die if abfrage nutzlos und bei 
einem sehr hohen Wert von count könnte es einen Stackoverflow geben. Ist 
dies möglich?

von Rolf M. (rmagnus)


Lesenswert?

Daniel A. schrieb:
> Ich habe gerade eine Situation in welcher das Relevant sein könnte:void
> func1(unsigned count){
>   if(count>20)
>     return;
>   {
>     int x[count];
>     func2(count,x);
>   }
> }
> Muss ich mir hier sorgen wegen einem möglichen Stackoverflow machen?

Kann ich mir nicht vorstellen. Kann ja auch sein, dass die Größe des 
Arrays zu Beginn der Funktion noch gar nicht bekannt ist. Zur Not 
gliedert man den Vergleich in eine externe Funktion aus:
1
unsigned check(unsigned count, unsigned max)
2
{
3
    return count > max ? 0 : count;
4
}
5
6
void func1(unsigned count){
7
  unsigned size = check(count, 20);
8
  if(size == 0)
9
    return;
10
  {
11
    int x[size];
12
    func2(size,x);
13
  }
14
}

So ist der Wert für die Array-Größe vor dem Check gar nicht bekannt, und 
es kann noch gar nicht anglegt werden, zumindest wenn man sicherstellt, 
dass die check-Funktion nicht inline expandiert wird.

von (prx) A. K. (prx)


Lesenswert?

Gute Frage. Aber besser wärs, das nicht erst zu versuchen.

Compiler neigen wie erwähnt dazu, lokale Arrays mit konstanter Grösse 
bereits am Anfang der Funktion zu allozieren, während solche mit 
variabler Grösse oft erst dort alloziert können, wo das tatsächlich 
steht.

Blöderweise ist es nicht immer leicht, dem Quellcode zu entnehmen, ob 
ein Ausdruck im Sinne des Compilers konstant ist. Also ob man sich 
wirklich davon abhängig machen will, dass
   const int N = 1000000;
   ... { int a{N]; }
in C variable Länge aber in C++ konstante Länge hat? Und das Array 
dementsprechend in C erst im Block, in C++ aber vorneweg alloziert wird.

Wenn die Abfrage vorher direkt auf N geht ist das noch ok, denn dann 
fliegt der Code ggf ganz raus. Aber wenn die Abfrage nur um Ecken auf N 
geht und der Compiler das nicht merkt, dann...

: Bearbeitet durch User
von Nase (Gast)


Lesenswert?

Daniel A. schrieb:
> Muss ich mir hier sorgen wegen einem möglichen Stackoverflow machen?
Nein, musst du nicht.

Das ist in C völlig wohldefiniert und erlaubt.
Der Speicher für den Vektor wird genau an der Stelle bereitgestellt, wo 
er in den Scope gelangt. Dabei bestimmt der Wert, den 'count' gerade 
dort hat, die Größe des Vektors.

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.