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

Lesezeit: 12 Minuten

Ich habe gehört (wahrscheinlich von einem Lehrer), dass man alle Variablen über dem Programm/der Funktion deklarieren sollte und dass das Deklarieren neuer unter den Anweisungen Probleme verursachen könnte.

Aber dann habe ich K&R gelesen und bin auf diesen Satz gestoßen: “Variablendeklarationen (einschließlich Initialisierungen) können der linken Klammer folgen, die jede zusammengesetzte Anweisung 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 also keine Variablen deklarieren? Was ist zum Beispiel, wenn meine Variablendeklaration nicht direkt nach der öffnenden geschweiften Klammer steht? Wie hier:

int main(){
    int x = 10;

    x++;
    printf("%d\n",x);

    int z = 6;
    printf("%d\n",z);
}

Kann das je nach Programm/Maschine zu Problemen führen?

  • gcc ist ziemlich locker. Sie verwenden c99-Arrays und -Deklarationen mit variabler Länge. Kompilieren mit gcc -std=c89 -pedantic und du wirst angeschrien. Laut c99 ist das alles koscher.

    – David

    12. Dezember 2011 um 12:23 Uhr

  • Das Problem ist, dass Sie K&R gelesen haben, das veraltet ist.

    – Ludin

    24. April 2015 um 11:04 Uhr

  • @Lundin Gibt es einen geeigneten Ersatz für K & R? Es gibt nichts nach der Ausgabe von ANSI C, und der Leser dieses Buches kann deutlich lesen, auf welchen Standard es sich bezieht

    – Brandin

    2. September 2015 um 18:02 Uhr

Benutzeravatar von hugomg
Umarmung

Ich höre auch oft, dass das Platzieren von Variablen am Anfang der Funktion der beste Weg ist, Dinge zu tun, aber ich bin absolut anderer Meinung. Ich ziehe es vor, Variablen auf den kleinstmöglichen Bereich zu beschränken, damit sie weniger wahrscheinlich missbraucht werden und ich weniger Zeug habe, das meinen mentalen Raum in jeder Zeile des Programms füllt.

Während alle Versionen von C einen lexikalischen Blockbereich zulassen, hängt der Ort, an dem Sie die Variablen deklarieren können, von der Version des C-Standards ab, auf den Sie abzielen:

Ab C99 oder C++

Moderne C-Compiler wie gcc und clang unterstützen die C99 und C11 Standards, die es Ihnen ermöglichen, eine Variable überall dort zu deklarieren, wo eine Anweisung hingehört. Der Gültigkeitsbereich der Variablen beginnt am Punkt der Deklaration bis zum Ende des Blocks (nächste schließende geschweifte 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 innerhalb von For-Loop-Initialisierern deklarieren. Die Variable wird nur innerhalb der Schleife existieren.

for(int i=0; i<10; i++){
    printf("%d", i);
}

ANSI-C (C90)

Wenn Sie auf die Älteren abzielen ANSI-C Standard, dann sind Sie darauf beschränkt, Variablen unmittelbar nach einer öffnenden geschweiften Klammer zu deklarieren1.

Dies bedeutet jedoch nicht, dass Sie alle Ihre Variablen am Anfang Ihrer Funktionen deklarieren müssen. In C können Sie einen durch geschweifte Klammern getrennten Block überall dort platzieren, wo eine Anweisung stehen könnte (nicht nur nach Dingen wie if oder for) und Sie können dies verwenden, um neue Variablenbereiche 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 die übergeben müssen --pedantic -Flag, um den C90-Standard tatsächlich durchzusetzen und sich darüber zu beschweren, dass die Variablen an der falschen Stelle deklariert sind. Wenn Sie nur verwenden -std=c90 es veranlasst gcc, eine Obermenge von C90 zu akzeptieren, was auch die flexibleren C99-Variablendeklarationen zulässt.

  • “Der Gültigkeitsbereich der Variablen beginnt vom 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 Stapelspeicherplatz effizient nutzt. Ich habe das ein paar Mal gesehen, und es ist eine falsche Schlussfolgerung aus dem falschen Refrain, dass C ein “tragbarer Assembler” ist. Weil (A) die Variable in einem Register zugewiesen werden kann, nicht auf dem Stack, & (B) wenn eine Variable auf dem Stack ist, aber der Compiler sehen kann, dass Sie sie nicht mehr verwenden, z. B. 10% des Weges durch einen Block, it kann diesen Platz leicht für etwas anderes recyceln.

    – Unterstrich_d

    10. August 2016 um 17:53 Uhr

  • @underscore_d Denken Sie daran, dass Menschen, die Speicher sparen möchten, häufig mit eingebetteten Systemen zu tun haben, bei denen man aufgrund von Zertifizierungs- und / oder Toolchain-Aspekten entweder gezwungen ist, sich an niedrigere Optimierungsstufen und / oder ältere Compiler-Versionen zu halten.

    – Klassenstapler

    25. Juni 2017 um 3:31 Uhr

  • 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, und wenn Sie versuchen, eine Variable in einer Zeile zu verwenden und sie in der nächsten Zeile zu deklarieren, erhalten Sie einen Kompilierungsfehler “Variable ist nicht deklariert”.

    – Umarmung

    23. Dezember 2017 um 15:20 Uhr


missingno behandelt, was ANSI C erlaubt, aber er geht nicht darauf ein, warum Ihre Lehrer Ihnen gesagt haben, dass Sie Ihre Variablen ganz oben in Ihren Funktionen deklarieren sollen. Das Deklarieren von Variablen an ungeraden Stellen kann das Lesen Ihres Codes erschweren, was zu Fehlern führen kann.

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 erklärt i zweimal. Genauer gesagt habe ich zwei Variablen deklariert, beide mit dem Namen i. Sie könnten denken, dass dies einen Fehler verursachen würde, aber das ist nicht der Fall, weil die beiden i Variablen befinden sich in unterschiedlichen Gültigkeitsbereichen. 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

Zuerst weisen wir 20 und 30 zu i und j beziehungsweise. Dann weisen wir innerhalb der geschweiften Klammern 88 und 99 zu j ihren Wert behalten, aber i wird er wieder 20? Das liegt an den zwei Unterschieden i Variablen.

Zwischen den inneren geschweiften Klammern die i Variable mit dem Wert 20 ist versteckt und unzugänglich, aber da haben wir keine neue deklariert jwir verwenden immer noch die j aus dem äußeren Bereich. Wenn wir die inneren geschweiften Klammern verlassen, wird die i das Halten des Werts 88 verschwindet, und wir haben wieder Zugriff auf die i mit dem Wert 20.

Manchmal ist dieses Verhalten gut, manchmal vielleicht auch nicht, aber es sollte klar sein, dass Sie Ihren Code wirklich verwirrend und schwer verständlich machen können, wenn Sie dieses Feature von C wahllos verwenden.

  • Sie haben Ihren Code schwer lesbar gemacht, weil Sie denselben Namen für zwei Variablen verwendet haben, nicht weil Sie Variablen nicht am Anfang der Funktion deklariert haben. Das sind zwei verschiedene Probleme. Ich bin absolut nicht einverstanden mit der Aussage, dass das Deklarieren von Variablen an anderen Stellen Ihren Code schwer lesbar macht, ich denke, das Gegenteil ist der Fall. Wenn Sie beim Schreiben von Code die Variable in der Nähe des Zeitpunkts deklarieren, an dem sie verwendet werden soll, und dem Prinzip der zeitlichen und räumlichen Lokalität folgen, können Sie beim Lesen sehr einfach identifizieren, was sie tut, warum sie vorhanden ist und wie sie verwendet wird.

    – Hawok

    3. Januar 2014 um 20:47 Uhr

  • Als Faustregel deklariere ich alle Variablen, die mehrfach im Block verwendet werden, am Anfang des Blocks. Irgendeine temporäre Variable, die irgendwo nur für eine lokale Berechnung dient, neige ich dazu, zu deklarieren, wo sie verwendet wird, da sie außerhalb dieses Ausschnitts nicht von Interesse ist.

    – Ludin

    24. April 2015 um 11:28 Uhr

  • Wenn Sie eine Variable dort deklarieren, wo sie benötigt wird, nicht unbedingt am Anfang eines Blocks, können Sie sie oft initialisieren. Statt { int n; /* computations ... */ n = some_value; } Du kannst schreiben { /* computations ... */ const int n = some_value; }.

    – Keith Thompson

    29. Juli 2016 um 14:53 Uhr

  • @Havok “Sie haben denselben Namen für zwei Variablen verwendet”, auch bekannt als “Schattenvariablen” (man gcc dann suchen -Wshadow). also ya ich stimme zu Shadowed variables wird hier demonstriert.

    – Trevor Boyd Smith

    22. Mai 2020 um 19:24 Uhr

Wenn Ihr Compiler dies zulässt, ist es in Ordnung, eine beliebige Stelle zu deklarieren. Tatsächlich ist der Code besser lesbar (IMHO), wenn Sie die Variable dort deklarieren, wo Sie sie verwenden, anstatt am Anfang einer Funktion, da dies das Erkennen von Fehlern erleichtert, z. B. das Vergessen, die Variable zu initialisieren oder die Variable versehentlich zu verbergen.

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 bei der ersten z-Definition, aber nicht bei der zweiten.

Allerdings mehrere Zeilen von:

//C99
for(int i=0; i<10; i++){}

funktioniert. Zeigt die Subtilität dieser C99-Regel.

Ich persönlich meide dieses C99-Feature leidenschaftlich.

Das Argument, dass es den Gültigkeitsbereich einer Variablen einschränkt, ist falsch, wie diese Beispiele zeigen. Nach der neuen Regel können Sie eine Variable nicht sicher deklarieren, bis Sie den gesamten Block gescannt haben, während Sie früher nur verstehen mussten, was am Kopf jedes Blocks vor sich ging.

Gemäß der C-Programmiersprache 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 können Sie sehen, dass es normalerweise kein Muss ist.

  • Heutzutage ist nicht alles C K&R – sehr wenig aktueller Code lässt sich mit alten K&R-Compilern kompilieren, also warum sollte man das als Referenz verwenden?

    – Toby Speight

    20. September 2017 um 9:35 Uhr

  • Die Klarheit und seine Fähigkeit zu erklären ist großartig. Ich denke, es ist gut, von Originalentwicklern zu lernen. Ja, es ist alt, aber es ist gut für Anfänger.

    – Gagandeep Kaur

    20. September 2017 um 17:04 Uhr

Benutzeravatar von Leslie Satenstein
Leslie Satenstein

Bei clang und gcc bin ich auf folgende Probleme gestoßen. 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 f1, f2 oder f3, um innerhalb des Blocks zu sein. Ich musste f1, f2, f3 in den Funktionsdefinitionsbereich verschieben. der Compiler störte sich nicht an der Definition einer ganzen Zahl mit dem Block.

  • Heutzutage ist nicht alles C K&R – sehr wenig aktueller Code lässt sich mit alten K&R-Compilern kompilieren, also warum sollte man das als Referenz verwenden?

    – Toby Speight

    20. September 2017 um 9:35 Uhr

  • Die Klarheit und seine Fähigkeit zu erklären ist großartig. Ich denke, es ist gut, von Originalentwicklern zu lernen. Ja, es ist alt, aber es ist gut für Anfänger.

    – Gagandeep Kaur

    20. September 2017 um 17:04 Uhr

Intern werden alle lokalen Variablen einer Funktion auf einem Stapel oder in CPU-Registern zugewiesen, und dann wechselt der generierte Maschinencode zwischen den Registern und dem Stapel (Registerüberlauf genannt), wenn der Compiler schlecht ist oder wenn die CPU nicht genügend Register hat Halten Sie alle Bälle in der Luft zu jonglieren.

Um Sachen auf dem Stack zuzuweisen, hat die CPU zwei spezielle Register, eines namens Stack Pointer (SP) und ein anderes – Base Pointer (BP) oder Frame Pointer (was den Stack-Frame bedeutet, der lokal zum aktuellen Funktionsumfang gehört). SP zeigt innerhalb der aktuellen Position auf einem Stapel, während BP auf den Arbeitsdatensatz (darüber) und die Funktionsargumente (darunter) zeigt. Wenn die Funktion aufgerufen wird, schiebt sie den BP der aufrufenden / übergeordneten Funktion auf den Stapel (auf den SP zeigt) und setzt den aktuellen SP als neuen BP, erhöht dann den SP um die Anzahl der Bytes, die von den Registern auf den Stapel übertragen werden, und führt die Berechnung durch , und bei der Rückkehr stellt es den BP seines Elternteils wieder her, indem es es vom Stack entfernt.

Im Allgemeinen behalten Sie Ihre Variablen in ihren eigenen {}-scope könnte die Kompilierung beschleunigen und den generierten Code verbessern, indem die Größe des Diagramms reduziert wird, das der Compiler durchlaufen muss, um zu bestimmen, welche Variablen wo und wie verwendet werden. In einigen Fällen (insbesondere wenn es sich um goto handelt) kann der Compiler die Tatsache übersehen, dass die Variable nicht mehr verwendet wird, es sei denn, Sie teilen dem Compiler ausdrücklich ihren Verwendungsbereich mit. Compiler könnten eine Zeit-/Tiefenbegrenzung haben, um den Programmgraphen zu durchsuchen.

Der Compiler könnte nahe beieinander deklarierte Variablen im selben Stack-Bereich platzieren, was bedeutet, dass beim Laden einer alle anderen vorab in den Cache geladen werden. Auf die gleiche Weise Variablen deklarieren registerkönnte dem Compiler einen Hinweis geben, dass Sie vermeiden möchten, dass die Variable um jeden Preis auf dem Stapel verschüttet wird.

Strenger C99-Standard erfordert explizite { vor Deklarationen, während Erweiterungen, die von C++ und GCC eingeführt wurden, es ermöglichen, vars weiter im Körper zu deklarieren, was komplizierter ist goto und case Aussagen. C++ erlaubt außerdem das Deklarieren von Dingen innerhalb der Schleifeninitialisierung, die auf den Umfang der Schleife beschränkt ist.

Zu guter Letzt wäre es für einen anderen Menschen, der Ihren Code liest, überwältigend, wenn er den oberen Teil einer Funktion mit einem halben Hundert Variablendeklarationen übersät sieht, anstatt sie an ihren Verwendungsstellen lokalisiert zu sehen. Es erleichtert auch das Auskommentieren ihrer Verwendung.

TLDR: verwenden {} Das explizite Angeben des Gültigkeitsbereichs von Variablen kann sowohl dem Compiler als auch dem menschlichen Leser helfen.

  • “Der strenge C99-Standard erfordert explizites {” ist nicht korrekt. Ich schätze, du meintest C89 dort. C99 erlaubt Deklarationen nach Anweisungen.

    – Mecki

    26. September 2020 um 0:38 Uhr

1419480cookie-checkWo können Sie neue Variablen in C deklarieren und wo nicht?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy