it-swarm.com.de

Platzierung der Variablendeklaration in C

Ich habe lange gedacht, dass in C alle Variablen am Anfang der Funktion deklariert werden müssen. Ich weiß, dass die Regeln in C99 dieselben sind wie in C++, aber wie lauten die Platzierungsregeln für Variablendeklarationen für C89/ANSI C?

Der folgende Code wird erfolgreich mit gcc -std=c89 Und gcc -ansi Kompiliert:

#include <stdio.h>
int main() {
    int i;
    for (i = 0; i < 10; i++) {
        char c = (i % 95) + 32;
        printf("%i: %c\n", i, c);
        char *s;
        s = "some string";
        puts(s);
    }
    return 0;
}

Sollten die Deklarationen von c und s im C89/ANSI-Modus keinen Fehler verursachen?

120
mcjabberz

Es wird erfolgreich kompiliert, da GCC es als GNU Erweiterung zulässt, auch wenn es nicht Teil des C89- oder ANSI-Standards ist. Wenn Sie diese Standards strikt einhalten möchten, müssen Sie das -pedantic Flagge.

137
mipadi

Für C89 müssen Sie alle Variablen am Anfang eines Bereichsblock deklarieren.

Also dein char c -Deklaration ist gültig, da sie sich am oberen Rand des Bereichsblocks für for-Schleifen befindet. Aber die char *s Deklaration sollte ein Fehler sein.

74
Kiley Hykawy

Das Gruppieren von Variablendeklarationen am oberen Rand des Blocks ist ein Erbe, das wahrscheinlich auf die Einschränkungen alter primitiver C-Compiler zurückzuführen ist. Alle modernen Sprachen empfehlen und erzwingen manchmal sogar die Deklaration lokaler Variablen zu dem spätesten Zeitpunkt: an dem sie zum ersten Mal initialisiert werden. Denn hierdurch wird das Risiko beseitigt, dass versehentlich ein Zufallswert verwendet wird. Die Trennung von Deklaration und Initialisierung verhindert auch, dass Sie "const" (oder "final") verwenden, wenn Sie könnten.

C++ akzeptiert aus Gründen der Abwärtskompatibilität mit C leider weiterhin die alte Methode der Top-Deklaration (eine C-Kompatibilität von vielen anderen ...). C++ versucht jedoch, sich davon zu entfernen:

  • Das Design von C++ - Referenzen erlaubt nicht einmal eine solche Top-of-the-Block-Gruppierung.
  • Wenn Sie die Deklaration und Initialisierung eines C++ - lokalen Objekts trennen, zahlen Sie die Kosten eines zusätzlichen Konstruktors für nichts. Wenn der Konstruktor no-arg nicht existiert, dürfen Sie nicht einmal beide trennen!

C99 beginnt, C in dieselbe Richtung zu bewegen.

Wenn Sie nicht wissen, wo lokale Variablen deklariert sind, haben Sie ein viel größeres Problem: Der umschließende Block ist zu lang und sollte geteilt werden.

https://www.securecoding.cert.org/confluence/display/cplusplus/DCL19-CPP.+Initialize+automatic+local+variables+on+declaration

29
MarcH

Unter dem Gesichtspunkt der Wartbarkeit und nicht der Syntax gibt es mindestens drei Gedankengänge:

  1. Deklarieren Sie alle Variablen am Anfang der Funktion, damit sie an einem Ort sind und Sie die umfassende Liste auf einen Blick sehen können.

  2. Deklarieren Sie alle Variablen so nah wie möglich an der Stelle, an der sie zuerst verwendet werden, damit Sie wissen, dass warum jede benötigt wird.

  3. Deklarieren Sie alle Variablen am Anfang des innersten Bereichsblocks, damit sie den Bereich so schnell wie möglich verlassen und der Compiler den Speicher optimieren kann, um Ihnen mitzuteilen, ob Sie sie versehentlich dort verwenden, wo Sie nicht beabsichtigt hatten.

Ich bevorzuge im Allgemeinen die erste Option, da die anderen mich oft dazu zwingen, den Code nach den Deklarationen abzusuchen. Das Definieren aller Variablen im Voraus erleichtert auch das Initialisieren und Beobachten von einem Debugger aus.

Manchmal deklariere ich Variablen innerhalb eines kleineren Bereichsblocks, aber nur aus gutem Grund, von denen ich nur sehr wenige habe. Ein Beispiel könnte nach einer fork() stehen, um Variablen zu deklarieren, die nur vom untergeordneten Prozess benötigt werden. Für mich ist dieser visuelle Indikator eine hilfreiche Erinnerung an ihren Zweck.

23
Adam Liss

Wie von anderen angemerkt, ist GCC diesbezüglich (und möglicherweise auch bei anderen Compilern, abhängig von den Argumenten, mit denen sie aufgerufen werden) auch im C89-Modus zulässig, es sei denn, Sie verwenden die pedantische Prüfung. Um ehrlich zu sein, gibt es nicht viele gute Gründe, nicht pedantisch zu sein. hochwertiger moderner Code sollte immer ohne Warnungen kompiliert werden (oder nur sehr wenige, bei denen Sie wissen, dass Sie etwas Bestimmtes tun, das dem Compiler als möglicher Fehler verdächtig ist). Wenn Sie Ihren Code also nicht mit einem umständlichen Setup kompilieren können, ist möglicherweise etwas Aufmerksamkeit erforderlich.

C89 verlangt, dass Variablen vor allen anderen Anweisungen in jedem Gültigkeitsbereich deklariert werden. Spätere Standards ermöglichen eine Deklaration, die näher am Gebrauch liegt (was sowohl intuitiver als auch effizienter sein kann), insbesondere die gleichzeitige Deklaration und Initialisierung einer Regelungsvariablen in 'for'-Schleifen.

6
Gaidheal

Wie bereits erwähnt, gibt es dazu zwei Denkschulen.

1) Deklarieren Sie alles an der Spitze der Funktionen, da das Jahr 1987 ist.

2) Am nächsten zur ersten Verwendung und im kleinstmöglichen Rahmen deklarieren.

Meine Antwort darauf lautet DO BOTH! Lassen Sie mich erklären:

Bei langen Funktionen macht 1) das Refactoring sehr schwierig. Wenn Sie in einer Codebasis arbeiten, in der die Entwickler gegen die Idee von Subroutinen sind, haben Sie zu Beginn der Funktion 50 Variablendeklarationen, und einige von ihnen sind möglicherweise nur ein "i" für eine for-Schleife, die ganz am Anfang steht Grund der Funktion.

Daraus entwickelte ich die Deklaration-at-the-Top-PTBS und versuchte, Option 2) religiös umzusetzen.

Ich bin auf die erste Option zurückgekommen, weil es eine Sache gibt: kurze Funktionen. Wenn Ihre Funktionen kurz genug sind, haben Sie nur wenige lokale Variablen, und da die Funktion kurz ist, werden sie, wenn Sie sie oben auf die Funktion setzen, immer noch in der Nähe der ersten Verwendung sein.

Außerdem wird das Anti-Pattern von "declare and set to NULL" (deklarieren und auf NULL setzen) aufgelöst, wenn Sie oben deklarieren möchten, aber keine für die Initialisierung erforderlichen Berechnungen durchgeführt haben, da die zu initialisierenden Elemente wahrscheinlich als Argumente empfangen werden.

Deshalb denke ich jetzt, dass Sie am Anfang der Funktionen deklarieren und so nah wie möglich an der ersten Verwendung sein sollten. Also beide! Und der Weg dazu führt über gut unterteilte Unterprogramme.

Wenn Sie jedoch an einer langen Funktion arbeiten, sollten Sie die Dinge, die der ersten Verwendung am nächsten kommen, in den Griff bekommen, da es auf diese Weise einfacher ist, Methoden zu extrahieren.

Mein Rezept ist das. Nehmen Sie für alle lokalen Variablen die Variable und verschieben Sie ihre Deklaration nach unten, kompilieren Sie sie und verschieben Sie sie dann kurz vor dem Kompilierungsfehler. Das ist die erste Verwendung. Tun Sie dies für alle lokalen Variablen.

int foo = 0;
<code that uses foo>

int bar = 1;
<code that uses bar>

<code that uses foo>

Definieren Sie nun einen Gültigkeitsbereichsblock, der vor der Deklaration beginnt, und verschieben Sie das Ende, bis das Programm kompiliert wird

{
    int foo = 0;
    <code that uses foo>
}

int bar = 1;
<code that uses bar>

>>> First compilation error here
<code that uses foo>

Dies kann nicht kompiliert werden, da es mehr Code gibt, der foo verwendet. Wir können feststellen, dass der Compiler den Code, der bar verwendet, durchgehen konnte, weil er foo nicht verwendet. An diesem Punkt gibt es zwei Möglichkeiten. Die mechanische besteht darin, das "}" nach unten zu bewegen, bis es kompiliert ist, und die andere Möglichkeit besteht darin, den Code zu überprüfen und festzustellen, ob die Reihenfolge geändert werden kann in:

{
    int foo = 0;
    <code that uses foo>
}

<code that uses foo>

int bar = 1;
<code that uses bar>

Wenn die Reihenfolge geändert werden kann, ist dies wahrscheinlich das, was Sie möchten, da dies die Lebensdauer temporärer Werte verkürzt.

Eine andere Sache zu beachten, muss der Wert von foo zwischen den Codeblöcken, die es verwenden, beibehalten werden, oder könnte es nur ein anderes foo in beiden sein. Beispielsweise

int i;

for(i = 0; i < 8; ++i){
    ...
}

<some stuff>

for(i = 3; i < 32; ++i){
    ...
}

Diese Situationen erfordern mehr als meine Prozedur. Der Entwickler muss den Code analysieren, um zu bestimmen, was zu tun ist.

Der erste Schritt ist jedoch, die erste Verwendung zu finden. Sie können dies visuell tun, aber manchmal ist es einfacher, die Deklaration zu löschen, zu kompilieren und sie vor der ersten Verwendung zurückzusetzen. Befindet sich diese erste Verwendung in einer if-Anweisung, platzieren Sie sie dort und prüfen Sie, ob sie kompiliert wird. Der Compiler identifiziert dann andere Verwendungen. Versuchen Sie, einen Bereichsblock zu erstellen, der beide Verwendungszwecke umfasst.

Nachdem dieser mechanische Teil erledigt ist, wird es einfacher zu analysieren, wo sich die Daten befinden. Wenn eine Variable in einem Block mit großem Gültigkeitsbereich verwendet wird, analysieren Sie die Situation und prüfen Sie, ob Sie dieselbe Variable nur für zwei verschiedene Dinge verwenden (wie ein "i", das für zwei for-Schleifen verwendet wird). Wenn die Verwendungen nicht in Beziehung stehen, erstellen Sie für jede dieser nicht in Beziehung stehenden Verwendungen neue Variablen.

0