Ich habe gehört (wahrscheinlich von einem Lehrer), dass man alle Variablen über dem Programm / der Funktion deklarieren sollte und dass das Deklarieren neuer Variablen unter den Anweisungen Probleme verursachen könnte.
Aber dann las ich K & R und stieß auf diesen Satz: "Deklarationen von Variablen (einschließlich Initialisierungen) können der linken Klammer folgen, die eine zusammengesetzte Aussage einführt, nicht nur die, die eine Funktion beginnt." Er folgt mit einem Beispiel:
if (n > 0){
int i;
for (i=0;i<n;i++)
...
}
Ich habe ein bisschen mit dem Konzept gespielt und es funktioniert sogar mit Arrays. Zum Beispiel:
int main(){
int x = 0 ;
while (x<10){
if (x>5){
int y[x];
y[0] = 10;
printf("%d %d\n",y[0],y[4]);
}
x++;
}
}
Wann genau darf ich keine Variablen deklarieren? Was ist zum Beispiel, wenn meine Variablendeklaration nicht direkt nach der öffnenden Klammer steht? Wie hier:
int main(){
int x = 10;
x++;
printf("%d\n",x);
int z = 6;
printf("%d\n",z);
}
Könnte dies je nach Programm / Maschine zu Problemen führen?
quelle
gcc
ist ziemlich locker. Sie verwenden c99-Arrays und -Deklarationen variabler Länge. Kompilieren Sie mitgcc -std=c89 -pedantic
und Sie werden angeschrien. Laut c99 ist das alles koscher.Antworten:
Ich höre auch oft, dass das Setzen von Variablen an die Spitze der Funktion der beste Weg ist, Dinge zu tun, aber ich bin absolut anderer Meinung. Ich ziehe es vor, Variablen auf den kleinstmöglichen Umfang zu beschränken, damit sie weniger missbraucht werden können und ich in jeder Zeile des Programms weniger Dinge habe, die meinen mentalen Raum ausfüllen.
Während alle Versionen von C einen lexikalischen Blockbereich zulassen, hängt das Deklarieren der Variablen von der Version des C-Standards ab, auf die Sie abzielen:
Ab C99 oder C ++
Moderne C-Compiler wie gcc und clang unterstützen die Standards C99 und C11 , mit denen Sie eine Variable überall dort deklarieren können, wo eine Anweisung hingehen könnte. Der Gültigkeitsbereich der Variablen beginnt am Punkt der Deklaration bis zum Ende des Blocks (nächste schließende Klammer).
if( x < 10 ){ printf("%d", 17); // z is not in scope in this line int z = 42; printf("%d", z); // z is in scope in this line }
Sie können auch Variablen für Schleifeninitialisierer deklarieren. Die Variable existiert nur innerhalb der Schleife.
for(int i=0; i<10; i++){ printf("%d", i); }
ANSI C (C90)
Wenn Sie auf den älteren ANSI C- Standard abzielen , können Sie Variablen unmittelbar nach einer öffnenden Klammer 1 deklarieren .
Dies bedeutet jedoch nicht, dass Sie alle Ihre Variablen oben in Ihren Funktionen deklarieren müssen. In C können Sie einen durch Klammern getrennten Block an einer beliebigen Stelle platzieren, an der sich eine Anweisung befinden könnte (nicht nur nach Dingen wie
if
oderfor
), und Sie können dies verwenden, um neue variable Bereiche einzuführen. Das Folgende ist die ANSI C-Version der vorherigen C99-Beispiele:if( x < 10 ){ printf("%d", 17); // z is not in scope in this line { int z = 42; printf("%d", z); // z is in scope in this line } } {int i; for(i=0; i<10; i++){ printf("%d", i); }}
1 Beachten Sie, dass Sie bei Verwendung von gcc das
--pedantic
Flag übergeben müssen, damit der C90-Standard tatsächlich durchgesetzt wird, und sich beschweren müssen, dass die Variablen an der falschen Stelle deklariert sind. Wenn Sie es nur verwenden-std=c90
, akzeptiert gcc eine Obermenge von C90, was auch die flexibleren C99-Variablendeklarationen ermöglicht.quelle
Missingno behandelt, was ANSI C erlaubt, aber er geht nicht darauf ein, warum Ihre Lehrer Ihnen gesagt haben, dass Sie Ihre Variablen oben in Ihren Funktionen deklarieren sollen. Das Deklarieren von Variablen an ungeraden Stellen kann das Lesen Ihres Codes erschweren und zu Fehlern führen.
Nehmen Sie den folgenden Code als Beispiel.
#include <stdio.h> int main() { int i, j; i = 20; j = 30; printf("(1) i: %d, j: %d\n", i, j); { int i; i = 88; j = 99; printf("(2) i: %d, j: %d\n", i, j); } printf("(3) i: %d, j: %d\n", i, j); return 0; }
Wie Sie sehen können, habe ich
i
zweimal erklärt . Genauer gesagt habe ich zwei Variablen deklariert, beide mit dem Nameni
. Sie könnten denken, dies würde einen Fehler verursachen, aber dies ist nicht der Fall, da sich die beideni
Variablen in unterschiedlichen Bereichen befinden. Sie können dies deutlicher sehen, wenn Sie sich die Ausgabe dieser Funktion ansehen.(1) i: 20, j: 30 (2) i: 88, j: 99 (3) i: 20, j: 99
Zunächst weisen wir 20 und 30 zu
i
undj
sind. Dann weisen wir in den geschweiften Klammern 88 und 99 zu. Warumj
behält das dann seinen Wert bei, wird aberi
wieder 20? Es liegt an den zwei verschiedeneni
Variablen.Zwischen den inneren geschweiften Klammern ist die
i
Variable mit dem Wert 20 ausgeblendet und nicht zugänglich. Da wir jedoch keine neue deklariert habenj
, verwenden wir immer noch diej
aus dem äußeren Bereich. Wenn wir den inneren Satz von geschweiften Klammern verlassen, verschwindet dasi
Halten des Wertes 88 und wir haben wieder Zugriff auf deni
mit dem Wert 20.Manchmal ist dieses Verhalten eine gute Sache, manchmal vielleicht auch nicht, aber es sollte klar sein, dass Sie Ihren Code wirklich verwirrend und schwer verständlich machen können, wenn Sie diese Funktion von C wahllos verwenden.
quelle
{ int n; /* computations ... */ n = some_value; }
du schreiben kannst{ /* computations ... */ const int n = some_value; }
.man gcc
dann suchen nach-Wshadow
). Ich stimme zu, dass hier Schattenvariablen demonstriert werden.Wenn Ihr Compiler dies zulässt, ist es in Ordnung, zu deklarieren, wo immer Sie möchten. Tatsächlich ist der Code besser lesbar (IMHO), wenn Sie die Variable deklarieren, die Sie verwenden, anstatt oben in einer Funktion, da dies das Erkennen von Fehlern erleichtert, z. B. das Vergessen, die Variable zu initialisieren, oder das versehentliche Ausblenden der Variablen.
quelle
Ein Beitrag zeigt den folgenden Code:
//C99 printf("%d", 17); int z=42; printf("%d", z); //ANSI C printf("%d", 17); { int z=42; printf("%d", z); }
und ich denke, die Implikation ist, dass diese gleichwertig sind. Sie sind nicht. Wenn int z am Ende dieses Codeausschnitts platziert wird, verursacht dies einen Neudefinitionsfehler gegenüber der ersten z-Definition, jedoch nicht gegenüber der zweiten.
Mehrere Zeilen von:
//C99 for(int i=0; i<10; i++){}
funktioniert. Zeigen der Subtilität dieser C99-Regel.
Persönlich meide ich diese C99-Funktion leidenschaftlich.
Das Argument, dass es den Umfang einer Variablen einschränkt, ist falsch, wie diese Beispiele zeigen. Nach der neuen Regel können Sie eine Variable erst dann sicher deklarieren, wenn Sie den gesamten Block gescannt haben, während Sie früher nur verstehen mussten, was am Anfang jedes Blocks vor sich ging.
quelle
for
ist ein irrelevanter Vergleich}
. Das ist es! Wenn Sie im ersten Beispiel weitere Zeilen hinzufügen möchten, diez
nach dem Druck verwendet werden, tun Sie dies innerhalb des Codeblocks und nicht außerhalb des Codeblocks. Sie müssen definitiv nicht "den gesamten Block scannen", um zu sehen, ob es in Ordnung ist, eine neue Variable zu definieren. Ich muss gestehen, dass das erste Snippet ein künstliches Beispiel ist, und ich neige dazu, es wegen der zusätzlichen Einkerbung, die es erzeugt, zu vermeiden. Das{int i; for(..){ ... }}
Muster ist jedoch etwas, das ich die ganze Zeit mache.Gemäß der Programmiersprache The C von K & R -
In C müssen alle Variablen deklariert werden, bevor sie verwendet werden, normalerweise am Anfang der Funktion vor ausführbaren Anweisungen.
Hier kann man Wort sehen, normalerweise ist es kein Muss.
quelle
Bei clang und gcc stieß ich auf folgende Hauptprobleme. gcc version 8.2.1 20181011 clang version 6.0.1
{ char f1[]="This_is_part1 This_is_part2"; char f2[64]; char f3[64]; sscanf(f1,"%s %s",f2,f3); //split part1 to f2, part2 to f3 }
Keiner der Compiler mochte es, wenn f1, f2 oder f3 innerhalb des Blocks waren. Ich musste f1, f2, f3 in den Funktionsdefinitionsbereich verschieben. Der Compiler hatte nichts gegen die Definition einer Ganzzahl mit dem Block.
quelle
Intern werden alle für eine Funktion lokalen Variablen auf einem Stapel oder innerhalb von CPU-Registern zugewiesen, und dann wird der generierte Maschinencode zwischen den Registern und dem Stapel ausgetauscht (Register Spill genannt), wenn der Compiler fehlerhaft ist oder wenn die CPU nicht über genügend Register verfügt Lass alle Bälle in der Luft jonglieren.
Um Inhalte auf dem Stapel zuzuweisen, verfügt die CPU über zwei spezielle Register, eines mit dem Namen Stack Pointer (SP) und eines mit dem Namen Base Pointer (BP) oder Frame Pointer (dh dem Stack Frame, der sich im aktuellen Funktionsumfang befindet). SP zeigt auf die aktuelle Position auf einem Stapel, während BP auf den Arbeitsdatensatz (darüber) und die Funktionsargumente (darunter) zeigt. Wenn die Funktion aufgerufen wird, wird der BP der Aufrufer- / Elternfunktion auf den Stapel geschoben (auf den SP zeigt), und der aktuelle SP wird als neuer BP festgelegt. Anschließend wird der SP um die Anzahl der von den Registern auf den Stapel verschütteten Bytes erhöht und die Berechnung durchgeführt Bei der Rückkehr wird der BP des übergeordneten Elements wiederhergestellt, indem es vom Stapel genommen wird.
{}
Wenn Sie Ihre Variablen in ihrem eigenen Bereich belassen, kann dies im Allgemeinen die Kompilierung beschleunigen und den generierten Code verbessern, indem die Größe des Diagramms verringert wird, das der Compiler durchlaufen muss, um zu bestimmen, welche Variablen wo und wie verwendet werden. In einigen Fällen (insbesondere wenn goto beteiligt ist) kann der Compiler die Tatsache übersehen, dass die Variable nicht mehr verwendet wird, es sei denn, Sie teilen dem Compiler ausdrücklich seinen Verwendungsbereich mit. Compiler können Zeit- / Tiefenbeschränkungen haben, um das Programmdiagramm zu durchsuchen.Der Compiler kann deklarierte Variablen in demselben Stapelbereich platzieren, was bedeutet, dass beim Laden einer Variablen alle anderen vorab in den Cache geladen werden. Auf die gleiche Weise kann das Deklarieren einer Variablen
register
dem Compiler einen Hinweis geben, dass Sie vermeiden möchten, dass diese Variable um jeden Preis auf dem Stapel verschüttet wird.Der strenge C99-Standard erfordert explizite
{
Vor-Deklarationen, während durch C ++ und GCC eingeführte Erweiterungen die Deklaration von Vars weiter im Hauptteil ermöglichen, was Komplikationengoto
undcase
Anweisungen erschwert . In C ++ können außerdem Inhalte für die Schleifeninitialisierung deklariert werden, was auf den Umfang der Schleife beschränkt ist.Last but not least wäre es für einen anderen Menschen, der Ihren Code liest, überwältigend, wenn er die Spitze einer Funktion sieht, die mit halben hundert Variablendeklarationen übersät ist, anstatt sie an ihren Verwendungsorten zu lokalisieren. Es macht es auch einfacher, ihre Verwendung zu kommentieren.
TLDR: Die
{}
explizite Angabe des Variablenbereichs kann sowohl dem Compiler als auch dem menschlichen Leser helfen.quelle