Hallo,
ich habe einen c-Quellcode gefunden, da steht das:
1
...
2
while(!exit){
3
inti=0;
4
...
5
}
Ist das denn ok, wenn man eine Variable in einer while-Schleife immer
wieder neu erstellt? Ich habe das noch nie gesehen, ich kannte das
bisher nur so:
1
inti;
2
...
3
while(!exit){
4
i=0;
5
...
6
}
Also, ich habe es probiert - es funktioniert beides. Aber dennoch finde
ich das erste etwas seltsam.
Das ist keine Scriptsprache sondern C. Die Variable wird natürlich nur
einmal erstellt (ein Register dafür reserviert soweit möglich oder auf
dem Stack angelegt). Sie wird in jedem Durchlauf auf 0 gesetzt.
Bastler schrieb:> Das ist keine Scriptsprache sondern C. Die Variable wird natürlich nur> einmal erstellt (ein Register dafür reserviert soweit möglich oder auf> dem Stack angelegt). Sie wird in jedem Durchlauf auf 0 gesetzt.
nicht wirklich. Da man es mit jedem Datentype machen kann (also auch
komplexe Objekte) wird das Objekt jedes mal erstellt und der Konstructor
aufgerufen.
das bei int der Optimierer etwas optimieren kann ja klar.
Student schrieb:> Also, ich habe es probiert - es funktioniert beides. Aber dennoch finde> ich das erste etwas seltsam.
Völlig normal und schon in K&R C drin. Variablen innerhalb der Blöcke zu
deklarieren macht sowohl dem Compiler als auch dem Programmierer klar,
dass sie nur dort gebraucht werden. Heutige Compiler kriegen das auch
selber spitz, damalige konnten von dieser Programmiertechnik
profitieren.
Im ersten Fall ist die Variable nur innerhalb des Blocks gültig, d.h.
innerhalb der geschweiften Klammern. Du kannst also nach der Schleife
nicht mehr darauf zugreifen.
Im anderen Fall ist die Variable außerhalb der Schleife definiert und
kann auch danach noch verwendet werden.
Allgemein sollte man Variablen immer so weit "innen" wie möglich
definieren, also mit möglichst kurzer Sichtbarkeit. Falls sie also nur
innerhalb der Schleife gebraucht wird, sollte sie auch nur dort gültig
sein. Das macht den Code übersichtlicher.
Bei dem Beispiel, würde ich sagen:
Wenn man i außerhalb der Schleife noch braucht, dann muss man sie
außerhalb definieren.
Ansonsten ist es immer gut, Variablem möglichst zu verbergen.
Die erste Variante ist also gut, die zweite eher böse.
A. K. schrieb:> K&R C drin
Das war mir noch nie aufgefallen, dass es das damals schon so gemacht
wurde und ich programmiere schon seit einem vierteljahrhundert in C -
und siehe, ich lerne immer wieder was neues dazu - krass.
Peter II schrieb:> Bastler schrieb:>> Das ist keine Scriptsprache sondern C. Die Variable wird natürlich nur>> einmal erstellt (ein Register dafür reserviert soweit möglich oder auf>> dem Stack angelegt). Sie wird in jedem Durchlauf auf 0 gesetzt.>> nicht wirklich. Da man es mit jedem Datentype machen kann (also auch> komplexe Objekte) wird das Objekt jedes mal erstellt und der Konstructor> aufgerufen.>> das bei int der Optimierer etwas optimieren kann ja klar.
Man sollte aber auch dazu sagen, dass das Objekt am Ende des Blocks,
sprich mit der schließenden Klammer, wieder zerstört wird. Der dabei
frei werdende Speicher auf dem Stack wird im nächsten Durchlauf
wiederverwendet.
Peter II schrieb:> (also auch komplexe Objekte) wird das Objekt jedes mal erstellt und der> Konstructor aufgerufen.
Falsch. C kennt weder Objekte noch Konstruktoren. Für C++ wäre Deine
Behauptung jedoch zutreffend.
Student schrieb:> Aber dennoch finde ich das erste etwas seltsam.
Es gibt Leute, die propagieren, alle Variablen, die eine Funktion
braucht, am Funktionsanfang zu deklarieren.
Und es gibt Leute, die propagieren, eine Variable erst dort zu
deklarieren, wo sie auch gebraucht wird.
Bei letzterem braucht man sich um Performance oder Stackverbrauch keine
Gedanken zu machen, wenn z.B. eine Funktion mehrere Schleifen enthält
und diese jeweils immer wieder dieselben Variablen deklarieren. Der
Conpiler optimiert das schon richtig.
Eine Funktion mit mehrerer Schleifen hat aber eh schon ein Problem:
unübersichtlich -> jeweils in eine eigene Funktion. Kann man dann ja
selber inlinen, wenn man meint, schlauer als der Compiler zu sein.
Hans schrieb:> Allgemein sollte man Variablen immer so weit "innen" wie möglich> definieren, also mit möglichst kurzer Sichtbarkeit. Falls sie also nur> innerhalb der Schleife gebraucht wird, sollte sie auch nur dort gültig> sein. Das macht den Code übersichtlicher.
Das find ich garnicht. Man muß, beim Lesen des Codes, zusätzlich zum
Variablennamen immer noch den Scope im Kopf mitführen.
Ist "i" immer noch das gleiche i wie eine Seite vorher oder hat da
jemand nur das int vergessen oder hab ichs überlesen? Also nochmal
zurückscrollen...
MfG Klaus
Tom schrieb:>> Ist "i" immer noch das gleiche i wie eine Seite vorher>> Dann ist die Funktion viiiiiiiel zu lang und komplex.
Oder der Monitor zu klein.
Klaus schrieb:> Ist "i" immer noch das gleiche i wie eine Seite vorher oder hat da> jemand nur das int vergessen oder hab ichs überlesen? Also nochmal> zurückscrollen..
Ist es nicht eher andersherum?
Um zu sehen, was es mit einer Variable auf sich hat, muss man maximal
bis zur ihrer Deklaration zurückscrollen. Je näher Deklaration und
Verwendung beieinander liegen, umso besser also.
Dennoch benutze ich solche eingeschränkten Scopes recht selten, weiß
aber nicht warum. Da muss ich vielleicht mal in mich gehen ;-)
Rufus Τ. F. schrieb:> Oder der Monitor zu klein.
Das ist meiner sicher, aber 30 bis 50 Zeilen aktiver Code einer Funktion
zusammen mit einer übersichtlichen Formatierung sowie diversen anderen
Fenstern und Toolbars übersteigen leicht auch größere Monitore. Und die
Komplexität einer Funktion mache ich nicht an der Länge, sondern an der
Komplexität fest.
Ich definiere lokale Variable immer am Anfang und lass mir vom Compiler
helfen, wenn ich eine übersehen habe oder eine nicht mehr brauche.
MfG Klaus
In C werden Variablen nicht erstellt, sondern benutzt. Für jede Variable
wird VOR dem Eintritt in den jeweiligen Block Speicher belegt.
1
int a;
2
3
main()
4
{
5
int b;
6
7
while (1)
8
{
9
int c;
10
}
11
}
Für a wird Speicher belegt, bevor die main() Funktion aufgerufen wird.
Für b wird ebenfalls Speicher belegt, bevor die main() Funktion
ausgeführt wird.
Für c wird Speicher belegt, bevor die while Schleife ausgeführt wird.
Soweit ich weiss, darf der Compiler sich aussuchen, wann genau dieses
VOR ist.
So oder so gibt es alle drei Variablen nur einmal. Die Variable c
entspricht einem Speicherplatz. Sie wird nicht bei jeden
Schleifendurchlauf neu erstellt oder initialisiert. Die Deklaration
innerhalb der Schleife sagt nur aus, dass sie außerhalb der Schleife
unbekannt ist.
Ich vermute, dass ein Verschieben unter die Variable b letztendlich
exakt den gleichen Maschinencode erzeugt. Denn der Prozessor
unterscheidet nicht zwischen Gültigkeitsbereichen.
denn hier kann für alle 3 Variablen der gleiche Speicher bzw. das
gleiche Register verwendet werden. Freilich war das früher wichtiger als
heute, denn Compiler wie GCC kriegen selber raus, in welchen
Codebereichen eine Variable verwendet wird und in welchen nicht. Früher
war es üblich, dem Compiler mit "register int x;" einen entsprechenden
Tipp zu geben - da konnte solcher Code entscheidend sein.
Stefan U. schrieb:> Für jede Variable> wird VOR dem Eintritt in den jeweiligen Block Speicher belegt.
Im real erzeugte Code geschieht das oft für alle zusammen an Anfang/Ende
der Funktion, also was Platz auf dem Stack und zu sichernde Register
angeht. Weil sonst unnötiger Code produziert wird.
Erzeugt (in C und C++) die Ausgabe:
Loop 0, counter 1
Loop 1, counter 2
Loop 2, counter 3
Soweit nicht überraschend. Jedoch verhält sich Java anders!:
Loop 0 , counter 1
Loop 1 , counter 1
Loop 2 , counter 1
Interessanter finde ich, was bei diesem Code passiert:
1
int loop=0;
2
while (loop<3)
3
{
4
int counter=0;
5
counter++;
6
printf("Loop %d, counter %d\n",loop,counter);
7
loop++;
8
}
9
return 0;
10
11
Erzeugt (in C, C++ und Java) die Ausgabe:
12
Loop 0, counter 1
13
Loop 1, counter 1
14
Loop 2, counter 1
Hier wird die Variable bei jedem Schleifendurchlauf initialisiert,
obwohl sie nur einmal (vor Eintritt in die Schleife) erzeugt wurde.
Ich liebe solche Corner-Cases :-)
Stefan U. schrieb:> Interessanter finde ich, was bei diesem Code passiert:>>[...]>> Hier wird die Variable bei jedem Schleifendurchlauf initialisiert,> obwohl sie nur einmal (vor Eintritt in die Schleife) erzeugt wurde.>> Ich liebe solche Corner-Cases :-)
Nach der Öffnenden Klammer der while ist doch nicht "vor Eintritt in die
Schleife", sondern genau am Anfang dieser... Das Ergebnis ist doch
plausibel, die Erzeugung (bzw. Allokierung...) und Initialisierung
erfolgt eben wie jedes andere Statement innerhalb der Schleife mit jedem
Durchlauf erneut.
> Man sollte nie eine nicht initialisierte Variable verwende
Sie ist doch initialisiert! Beim ersten Schleifendurchlauf initialisiere
ich sie mit 0. Erst danach wird sie (beim incrementieren) zum ersten mal
gelesen.
> die Erzeugung (bzw. Allokierung...) und Initialisierung> erfolgt eben wie jedes andere Statement innerhalb der Schleife mit> jedem Durchlauf erneut.
Bei C/C++ eben nicht! Darum geht es ja in diesem Thread. Die Variable
wird nicht innerhalb der Schleife erezugt, sondern vorher.
Deine Aussage trifft wohl auf Java zu, aber nicht auf C/C++. Das beweist
mein erster Test.
Stefan U. schrieb:> Sie ist doch initialisiert! Beim ersten Schleifendurchlauf initialisiere> ich sie mit 0. Erst danach wird sie (beim incrementieren) zum ersten mal> gelesen.
das sagst du. Ich behaupte die variabel ist in jedem Durchlauf "neu".
Damit nicht initialisiert.
Nur weil sie zufällig immer an der gleichen Stelle im Speicher seht,
kann man sich doch nicht darauf verlassen. Was ist wenn die schleife
aufgerollt wird?
[c]
{
int counter;
counter=0;
counter++;
printf("Loop %d, counter %d\n",loop,counter);
}
{
int counter;
counter++;
printf("Loop %d, counter %d\n",loop,counter);
}
{
int counter;
counter++;
printf("Loop %d, counter %d\n",loop,counter);
}
[c]
dann kommt das raus.
Michael schrieb:> die Erzeugung (bzw. Allokierung...) und Initialisierung> erfolgt eben wie jedes andere Statement innerhalb der Schleife mit jedem> Durchlauf erneut.
Die Allokation lokaler Variablen darf wie hier zu sehen auch ausserhalb
des Gültigkeitsbereichs stattfinden. Die Initialisierung ist ein davon
unabhängiges Thema, das hat nichts miteinander zu tun.
Markus schrieb:> Es gibt Leute, die propagieren, alle Variablen, die eine Funktion> braucht, am Funktionsanfang zu deklarieren.> Und es gibt Leute, die propagieren, eine Variable erst dort zu> deklarieren, wo sie auch gebraucht wird.
Das hat wenig mit propagieren zu tun sondern eher damit, daß frühe C
Compiler verlangt haben, daß lokale Variablen am Anfang des Blocks
deklariert werden. Erst C99 (der C-Standard von 1999) erlaubt offiziell,
daß man Variablen an beliebiger Stelle eines Blocks deklarieren kann:
https://de.wikipedia.org/wiki/Varianten_der_Programmiersprache_C#Neuerungen_von_C99
Lesbarer ist es allemal, wenn man Variablen erst dann deklariert wenn
man sie braucht.
Stefan U. schrieb:>> die Erzeugung (bzw. Allokierung...) und Initialisierung>> erfolgt eben wie jedes andere Statement innerhalb der Schleife mit>> jedem Durchlauf erneut.>> Bei C/C++ eben nicht!
Doch wird sie.
Stefan du solltest noch mal deine Literatur studieren. Du hast da ein
paar sehr gefaehrliche und sehr falsche Fehlansichten ueber C.
Die Variable kommt konzeptionell bei jedem Schleifendurchlauf erneut zur
Welt und wird am Ende jedes Schleifendurchlaufs zerstoert. Dass ein
Compiler das auch unter Umstaenden abaendern kann, wenn *es im Ergebnis
auf das gleiche rauskommt* ist ein davon losgeloestes Thema!
Da eine lokale Variable ohne Initialisierung irgendeinen Wert hat, ist
es auch ok, wenn sie zufaellig den Wert von vorher hat. Das kann so
sein, das muss aber nicht so sein. Irgendein Wert ist nun mal irgendener
Ich versuche gerade, die Spezifikation dazu zu finden.
Für eine Variable, die innerhalb eine Schleife deklariert ist: Ist die
Variable nur für einen oder für alle Wiederholungen der Schleife gültig?
Bei meinem Test war sie für alle Wiederholungen gültig. Peter II meint,
dass dies nur Zufall war.
Gefunden habe ich das:
http://eli-project.sourceforge.net/c_html/c.html#s8.3
An Environment value for all identifiers in a block scope is therefore
created either at the beginning of the block... In either case, the
value is discarded at the end of the block.
Nur, was genau ist "the end of the block"? Wenn eine While Schleife
wiederholt wird, trifft die Auführung dabei wiederholt auf das Ende des
Blockes, oder erst danach, wenn die Schleife verlassen wird?
Woanders in der Spezifikation habe ich das gefunden:
RULE: iteration_statement ::= 'while' '(' Expression ')' statement
Wenn die while Schleife EIN Statement wiederholt ausführt und dieses
Statement auch ein ganzer Block sein darf, dann verstehe ich es so, dass
der Block mehmals ausgeführt wird. Also wird er merhmals begonnen und
mehrmals beendet. Womit ich nun Peter II zustimme.
Ist meine Schlussfolgerung richtig?
Bastler Aussage
> Die Variable wird natürlich nur einmal erstellt
konnte ich durch meinen Test bestätigen, aber wohl nur wegen eines
Zufalls. Diese Aussage ist wohl doch falsch.
Meine Aussage "Für c wird Speicher belegt, bevor die while Schleife
ausgeführt wird." war ebenso falsch.
Stefan U. schrieb:> Ich versuche gerade, die Spezifikation dazu zu finden.>> Für eine Variable, die innerhalb eine Schleife deklariert ist: Ist die> Variable nur für einen oder für alle Wiederholungen der Schleife gültig?
Fuer einen Durchlauf.
Der Block wird jedesmal neu betreten und verlassen
Stefan U. schrieb:> Ich versuche gerade, die Spezifikation dazu zu finden.>> Für eine Variable, die innerhalb eine Schleife deklariert ist: Ist die> Variable nur für einen oder für alle Wiederholungen der Schleife gültig?>> Bei meinem Test war sie für alle Wiederholungen gültig. Peter II meint,> dass dies nur Zufall war.>> Gefunden habe ich das:> http://eli-project.sourceforge.net/c_html/c.html#s8.3>> An Environment value for all identifiers in a block scope is therefore> created either at the beginning of the block... In either case, the> value is discarded at the end of the block.>> Nur, was genau ist "the end of the block"? Wenn eine While Schleife> wiederholt wird
Der springende Punkt ist, dass die while Schleife (alle Compound
Statements) nichts von einem Block weiss! Ein while wiederholt ein
Statement!
Das dieses Statement zufaellig dergestalt ist, dass es aus Blockklammern
und mehreren dadurch gruppierten Statements besteht, interessiert das
while nicht. Die { } Klammern gehoeren NICHT zum while, sondern bilden
das Statement.
Deine letzte Schlussfolgerung war, soweit ich das sehen kann, richtig
Stefan U. schrieb:> Meine Aussage "Für c wird Speicher belegt, bevor die while Schleife> ausgeführt wird." war ebenso falsch.
Dies kann der Fall sein, muss aber nicht. Entscheidend ist das:
Karl H. schrieb:> Die Variable kommt konzeptionell bei jedem Schleifendurchlauf erneut zur> Welt und wird am Ende jedes Schleifendurchlaufs zerstoert. Dass ein> Compiler das auch unter Umstaenden abaendern kann, wenn *es im Ergebnis> auf das gleiche rauskommt* ist ein davon losgeloestes Thema!
Der Standard beschreibt das Normverhalten anhand einer Pseudomaschine
(abstract virtual machine). Gleichzeitig gilt aber auch die "as if
rule", d.h. solange das Verhalten der erzeugten Codes auf der realen
Maschine innerhalb der Spezifikation zu diesem Normverhalten passt, hat
der Compiler die Freiheit, es anders zu implementieren als diese
Pseudomaschine nahelegt.
In
1
{intx=0;
2
...
3
}
ist zweierlei drin: Die Allokation und die Initialisierung.
Die Initialisierung muss an dieser Stelle stattfinden, sonst stimmt das
Verhalten nicht.
Die Allokation hingegen darf an dieser Stelle stattfinden, darf aber
auch vorher stattfinden, weil das am Verhalten im Rahmen der C
Spezifikation nichts ändert. Die Adresse oder die Registernummer darf
bei jedem Durchlauf unterschiedlich sein, darf aber auch gleich sein.
Beim oben aufgeführten loop unrolling wird die Variable oft durch einen
Satz von verschiedenen Registern rotieren.
Um die Sache noch etwas komplizierter zu gestalten:
Die tatsächliche Allokation von Variablen wird sich bei stark
optimierenden Compilern nicht selten auch innerhalb der Gültigkeit der
Variablen ändern. Oder auch vollständig entfallen.
Eine Variable, die am Anfang einer Funktion definiert wird, wird nicht
notwendigerweise an dieser Stelle alloziert. Das wird sie bei Registern
oft erst an jener Stelle, an der sie tatsächlich zum ersten Mal
verwendet wird.
Wobei man das nicht lexikalisch genau nehmen sollte, denn in "int x=0;"
wird der Compiler vielleicht erst einmal überhaupt nichts erzeugen,
sondern sich nur merken, dass es ein x mit Wert 0 gibt. Erst dort, wo
das x verwendet wird, wird er dann vielleicht ein grad freies Register
mit 0 belegen, oder vielleicht auch direkt den Wert 0 dort einsetzen, wo
x steht. Mit der Folge, dass vielleicht nie irgendein Platz für x
alloziert wird. [Dies ist ein Beispiel für linearen Code, bei Schleifen
sieht das wieder anders aus]
Zudem kann sich die Allokation auch mittendrin ändern, um beispielsweise
ein anderweitig dringender benötigtes Register frei zu bekommen. Bloss
um dann später vielleicht in einem anderen Register zu landen, oder auch
im Speicher.
Folgen hat das für Debugging mit eingeschalteter Optimierung: Compiler
und Debugger haben dann recht viel Mühe, die notwendige Information so
zu erzeugen und auszuwerten, das der Debugger stets an der richtigen
Stelle nachsieht.
Einmal mehr die "as if rule": Der Compiler muss nur dafür sorgen, dass
das spezifizierte Verhalten eingehalten wird. Der ist jedoch nicht
gezwungen, den Maschinencode exakt in der Reihenfolge des Quellcodes zu
erzeugen.
Yalu X. schrieb:> Klaus schrieb:>> Ist "i" immer noch das gleiche i wie eine Seite vorher oder hat da>> jemand nur das int vergessen oder hab ichs überlesen? Also nochmal>> zurückscrollen..>> Ist es nicht eher andersherum?>> Um zu sehen, was es mit einer Variable auf sich hat, muss man maximal> bis zur ihrer Deklaration zurückscrollen. Je näher Deklaration und> Verwendung beieinander liegen, umso besser also.>> Dennoch benutze ich solche eingeschränkten Scopes recht selten, weiß> aber nicht warum. Da muss ich vielleicht mal in mich gehen ;-)
Vielleicht solltest Du mehr C++ programmieren. ;-)
Dort entscheiden die Scopes, wann die Konstruktoren und Destruktoren
aufgerufen werden. Wenn man sämtliche Objekte gleich zur Beginn der
Funktion anlegt (und implizit erst am Ende der Funktion zerstört) kann
das deutlich Performance kosten. Beispielsweise, wenn man ein teuer zu
konstruierendes Objekt in manchen Zweigen gar nicht braucht. Oder einen
stark gewachsenen Container mit seinem Heap-Speicher unnötig spät
freigibt und deshalb ingesamt mehr Speicher benötigt.
Dabei geht es nicht nur um Speicher, sondern ggf. auch andere Ressourcen
(Stichwort RAII). Ein gutes Beispiel sind Lock-Guards, also Objekte, die
ein Mutex im Konstruktor belegen und im Destruktor wieder freigebenen.
Die möchte man möglichst eng um die Stelle legen, die auf die zu
schützenden Objekte zugreift, und nicht die komplette Funktion über
halten. Das kann einen massiven Unterschied in der
Multithreading-Performance bewirken.
Manchmal macht es sogar Sinn, einen freistehenden Block ohne
if/switch/while/for anzulegen, einfach nur um den Scope zu begrenzen,
innerhalb dessen ein Objekt gültig ist. Wenn man das eine Weile in C++
macht, gewöhnt man sich auch in C an, die Variablen strikt im Scope zu
begrenzen, auch wenn es dort weniger starke Auswirkungen hat.
Hans schrieb:> Wenn man sämtliche Objekte gleich zur Beginn der Funktion anlegt (und> implizit erst am Ende der Funktion zerstört) kann das deutlich> Performance kosten.
Auch der umgekehrte Fall ist möglich: In einer Schleife unnötigerweise
tausendmal ein Objekt zu konstruieren und zu destruieren kostet mehr
Zeit als dies jeweils nur einmal außerhalb der Schleife zu tun und
dasselbe Objekt tausendmal widerzuverwenden. Es hängt halt immer vom
konkreten Anwendungsfall ab.
> Dabei geht es nicht nur um Speicher, sondern ggf. auch andere Ressourcen> (Stichwort RAII). Ein gutes Beispiel sind Lock-Guards, also Objekte, die> ein Mutex im Konstruktor belegen und im Destruktor wieder freigebenen.
Für solche Dinge verwende auch ich eng begrenzte Scopes, da diese
Vorgehensweise nicht nur eleganter ist, sondern auch Fehler durch
vergessene Rückgabe der Ressource bereits im Keim erstickt.
> Manchmal macht es sogar Sinn, einen freistehenden Block ohne> einfach nur um den Scope zu begrenzen
Das mache ich oft, auch in anderen Programmiersprachen.