Wo können und können Sie neue Variablen in C deklarieren?

76

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:

Ich habe ein bisschen mit dem Konzept gespielt und es funktioniert sogar mit Arrays. Zum Beispiel:

Wann genau darf ich keine Variablen deklarieren? Was ist zum Beispiel, wenn meine Variablendeklaration nicht direkt nach der öffnenden Klammer steht? Wie hier:

Könnte dies je nach Programm / Maschine zu Problemen führen?

Daniel Scocco
quelle
5
gccist ziemlich locker. Sie verwenden c99-Arrays und -Deklarationen variabler Länge. Kompilieren Sie mit gcc -std=c89 -pedanticund Sie werden angeschrien. Laut c99 ist das alles koscher.
Dave
6
Das Problem ist, dass Sie K & R gelesen haben, was veraltet ist.
Lundin
1
@Lundin Gibt es einen geeigneten Ersatz für K & R? Nach der ANSI C-Ausgabe gibt es nichts mehr, und der Leser dieses Buches kann deutlich lesen, auf welchen Standard es sich bezieht
Brandin

Antworten:

125

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).

Sie können auch Variablen für Schleifeninitialisierer deklarieren. Die Variable existiert nur innerhalb der Schleife.

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 ifoder for), und Sie können dies verwenden, um neue variable Bereiche einzuführen. Das Folgende ist die ANSI C-Version der vorherigen C99-Beispiele:


1 Beachten Sie, dass Sie bei Verwendung von gcc das --pedanticFlag ü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.

Hugomg
quelle
1
"Der Gültigkeitsbereich der Variablen beginnt am Punkt der Deklaration bis zum Ende des Blocks" - was, falls sich jemand wundert, nicht bedeutet, dass das manuelle Erstellen eines schmaleren Blocks nützlich / erforderlich ist, damit der Compiler den Stapelspeicher effizient nutzt. Ich habe das schon ein paar Mal gesehen und es ist eine falsche Schlussfolgerung aus dem falschen Refrain, dass C 'portabler Assembler' ist. Weil (A) die Variable möglicherweise in einem Register zugewiesen wird, nicht auf dem Stapel, und (B) wenn sich eine Variable auf dem Stapel befindet, der Compiler jedoch sehen kann, dass Sie sie nicht mehr verwenden, z. B. 10% des Weges durch einen Block kann diesen Raum leicht für etwas anderes recyceln.
underscore_d
3
@underscore_d Beachten Sie, dass Personen, die Speicherplatz sparen möchten, häufig mit eingebetteten Systemen arbeiten, bei denen man aufgrund von Zertifizierungs- und / oder Toolchain-Aspekten entweder gezwungen ist, sich an niedrigere Optimierungsstufen und / oder ältere Compilerversionen zu halten.
Klassenstapler
Nur weil Sie eine Variable in der Mitte eines Bereichs deklarieren, wird ihr Bereich nicht kürzer. Es macht es nur schwieriger zu erkennen, welche Variablen im Geltungsbereich enthalten sind und welche nicht. Was Bereiche kürzer macht, ist die Anonymisierung von Bereichen, die nicht in der Mitte eines Bereichs deklariert werden (was nur ein Hack ist, der die Deklaration effektiv nach oben verschiebt und die Zuweisung an Ort und Stelle hält, macht es nur schwieriger, über die Umgebung des Bereichs nachzudenken scope, was effektiv isomorph ist, um in jedem Bereich eine anonyme Struktur zu haben, die das Produkt aller deklarierten Variablen ist).
Dmitry
2
Ich weiß nicht, woher Sie die Idee haben, dass das Deklarieren von Variablen in der Mitte eines Bereichs nur ein "Hack ist, der die Deklaration effektiv nach oben verschiebt". Dies ist nicht der Fall. Wenn Sie versuchen, eine Variable in einer Zeile zu verwenden und in der nächsten Zeile zu deklarieren, wird der Kompilierungsfehler "Variable ist nicht deklariert" angezeigt.
Hugomg
3

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.

Wie Sie sehen können, habe ich izweimal erklärt . Genauer gesagt habe ich zwei Variablen deklariert, beide mit dem Namen i. Sie könnten denken, dies würde einen Fehler verursachen, aber dies ist nicht der Fall, da sich die beiden iVariablen in unterschiedlichen Bereichen befinden. Sie können dies deutlicher sehen, wenn Sie sich die Ausgabe dieser Funktion ansehen.

Zunächst weisen wir 20 und 30 zu iund jsind. Dann weisen wir in den geschweiften Klammern 88 und 99 zu. Warum jbehält das dann seinen Wert bei, wird aber iwieder 20? Es liegt an den zwei verschiedenen iVariablen.

Zwischen den inneren geschweiften Klammern ist die iVariable mit dem Wert 20 ausgeblendet und nicht zugänglich. Da wir jedoch keine neue deklariert haben j, verwenden wir immer noch die jaus dem äußeren Bereich. Wenn wir den inneren Satz von geschweiften Klammern verlassen, verschwindet das iHalten des Wertes 88 und wir haben wieder Zugriff auf den imit 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.

haydenmuhl
quelle
30
Sie haben das Lesen Ihres Codes erschwert, weil Sie für zwei Variablen denselben Namen verwendet haben, nicht weil Sie Variablen nicht am Anfang der Funktion deklariert haben. Das sind zwei verschiedene Probleme. Ich bin mit der Aussage, dass das Deklarieren von Variablen an anderen Stellen das Lesen Ihres Codes erschwert, überhaupt nicht einverstanden. Ich denke, das Gegenteil ist der Fall. Wenn Sie beim Schreiben von Code die Variable in der Nähe deklarieren, wann sie verwendet werden soll, können Sie beim Lesen nach dem Prinzip der zeitlichen und räumlichen Lokalität beim Lesen feststellen, was sie tut, warum sie vorhanden ist und wie sie sehr einfach verwendet wird.
Havok
3
Als Faustregel deklariere ich alle Variablen, die am Anfang des Blocks mehrmals im Block verwendet werden. Bei einer temporären Variablen, die nur für eine lokale Berechnung gedacht ist, neige ich dazu zu deklarieren, wo sie verwendet wird, da sie außerhalb dieses Snippets nicht von Interesse ist.
Lundin
5
Wenn Sie eine Variable dort deklarieren, wo sie benötigt wird, nicht unbedingt am Anfang eines Blocks, können Sie sie häufig initialisieren. Anstatt dass { int n; /* computations ... */ n = some_value; }du schreiben kannst { /* computations ... */ const int n = some_value; }.
Keith Thompson
@Havok "Sie haben denselben Namen für zwei Variablen verwendet", auch bekannt als "Schattenvariablen" ( man gccdann suchen nach -Wshadow). Ich stimme zu, dass hier Schattenvariablen demonstriert werden.
Trevor Boyd Smith
1

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.

AndersK
quelle
0

Ein Beitrag zeigt den folgenden Code:

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:

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.

user2073625
quelle
1
Die meisten anderen Personen, die bereit sind, die Verantwortung für die Verfolgung ihres Codes zu übernehmen, begrüßen es, mit offenen Armen überall zu deklarieren, da dies viele Vorteile für die Lesbarkeit bietet. Und forist ein irrelevanter Vergleich
underscore_d
Es ist nicht so kompliziert, wie Sie es klingen lassen. Der Gültigkeitsbereich einer Variablen beginnt bei ihrer Deklaration und endet bei der nächsten }. Das ist es! Wenn Sie im ersten Beispiel weitere Zeilen hinzufügen möchten, die znach 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.
Hugomg
Ihre Behauptung ist ungenau, da Sie im zweiten Code-Snippt (ANSI C) nicht einmal eine zweite Deklaration von int z am unteren Rand des ANSI C-Blocks einfügen können, da Sie mit ANSI C nur variable Deklarationen am oberen Rand einfügen können. Der Fehler ist also unterschiedlich, aber das Ergebnis ist das gleiche. Sie können int z nicht am Ende eines dieser Codefragmente einfügen.
RTHarston
Was ist das Problem, wenn mehrere Zeilen dieser for-Schleife vorhanden sind? Das int i lebt nur innerhalb des Blocks dieser for-Schleife, so dass es keine Undichtigkeiten und keine wiederholten Definitionen des int i gibt.
RTHarston
0

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.

Gagandeep Kaur
quelle
Heutzutage ist nicht alles C K & R - nur sehr wenig aktueller Code wird mit alten K & R-Compilern kompiliert. Warum sollten Sie das als Referenz verwenden?
Toby Speight
Die Klarheit und Erklärungsfähigkeit ist beeindruckend. Ich denke, es ist gut, von Originalentwicklern zu lernen. Ja, es ist uralt, aber es ist gut für Anfänger.
Gagandeep Kaur
0

Bei clang und gcc stieß ich auf folgende Hauptprobleme. gcc version 8.2.1 20181011 clang version 6.0.1

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.

Leslie Satenstein
quelle
0

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 registerdem 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 Komplikationen gotound caseAnweisungen 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.

SmugLispWeenie
quelle
"Strenger C99-Standard erfordert explizites {" ist nicht korrekt. Ich denke du meintest dort C89. C99 erlaubt Deklarationen nach Aussagen.
Mecki