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ß
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.
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.
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.
Ü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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
Ich habe gerade eine Situation in welcher das Relevant sein könnte:
1
voidfunc1(unsignedcount){
2
if(count>20)
3
return;
4
{
5
intx[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?
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
unsignedcheck(unsignedcount,unsignedmax)
2
{
3
returncount>max?0:count;
4
}
5
6
voidfunc1(unsignedcount){
7
unsignedsize=check(count,20);
8
if(size==0)
9
return;
10
{
11
intx[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.
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...
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.