Ich habe lange gedacht, dass in C alle Variablen am Anfang der Funktion deklariert werden müssen. Ich weiß, dass in C99 die Regeln dieselben sind wie in C ++, aber wie lauten die Regeln für die Platzierung von Variablendeklarationen für C89 / ANSI C?
Der folgende Code wird erfolgreich mit gcc -std=c89
und kompiliert gcc -ansi
:
#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 nicht s
einen Fehler im C89 / ANSI-Modus verursachen?
c
declaration
c89
mcjabberz
quelle
quelle
Antworten:
Es wird erfolgreich kompiliert, da GCC die Deklaration
s
als GNU-Erweiterung zulässt , obwohl es nicht Teil des C89- oder ANSI-Standards ist. Wenn Sie diese Standards strikt einhalten möchten, müssen Sie die-pedantic
Flagge übergeben.Die Deklaration
c
am Anfang eines{ }
Blocks ist Teil des C89-Standards; Der Block muss keine Funktion sein.quelle
s
eine Erweiterung ist (aus Sicht von C89). Die Erklärung vonc
ist in C89 vollkommen legal, es sind keine Erweiterungen erforderlich.Für C89 müssen Sie alle Ihre Variablen am Anfang eines Bereichsblocks deklarieren .
Ihre
char c
Deklaration ist also gültig, da sie sich oben im for-Schleifenbereichsblock befindet. Diechar *s
Erklärung sollte jedoch ein Fehler sein.quelle
gcc
. Vertrauen Sie also nicht darauf, dass ein Programm so kompiliert werden kann, dass es kompatibel ist.Das Gruppieren von Variablendeklarationen am oberen Rand des Blocks ist ein Erbe, das wahrscheinlich auf Einschränkungen alter, primitiver C-Compiler zurückzuführen ist. Alle modernen Sprachen empfehlen und erzwingen manchmal sogar die Deklaration lokaler Variablen spätestens dort, wo sie zuerst initialisiert werden. Weil dadurch das Risiko beseitigt wird, versehentlich einen zufälligen Wert zu verwenden. Das Trennen von Deklaration und Initialisierung verhindert auch, dass Sie "const" (oder "final") verwenden, wenn Sie könnten.
C ++ akzeptiert leider weiterhin die alte Top-Deklarationsmethode für die Abwärtskompatibilität mit C (eine C-Kompatibilität zieht sich aus vielen anderen heraus ...). Aber C ++ versucht, sich davon zu entfernen:
C99 beginnt, C in dieselbe Richtung zu bewegen.
Wenn Sie befürchten, nicht zu finden, wo lokale Variablen deklariert sind, haben Sie ein viel größeres Problem: Der umschließende Block ist zu lang und sollte aufgeteilt werden.
https://wiki.sei.cmu.edu/confluence/display/c/DCL19-C.+Minimize+the+scope+of+variables+and+functions
quelle
int y; ... if (x) { printf("X was true"); y=23;} return y;
...null
, ist All-Bit-Null häufig ein nützlicher Trap-Wert. In Sprachen, in denen explizit angegeben wird, dass Variablen standardmäßig alle Bits Null sind, ist die Abhängigkeit von diesem Wert kein Fehler . Compiler neigen noch nicht dazu, mit ihren "Optimierungen" übermäßig verrückt zu werden, aber Compiler-Autoren versuchen immer wieder, immer klüger zu werden. Eine Compileroption zum Initialisieren von Variablen mit absichtlichen Pseudozufallsvariablen kann nützlich sein, um Fehler zu identifizieren. Wenn Sie jedoch den Speicher nur auf seinem letzten Wert belassen, können Fehler manchmal maskiert werden.Unter dem Gesichtspunkt der Wartbarkeit und nicht der Syntaktik gibt es mindestens drei Gedankengänge:
Deklarieren Sie alle Variablen am Anfang der Funktion, damit sie sich an einer Stelle befinden und Sie die umfassende Liste auf einen Blick sehen können.
Deklarieren Sie alle Variablen so nah wie möglich an dem Ort, an dem sie zuerst verwendet werden, damit Sie wissen, warum sie benötigt werden.
Deklarieren Sie alle Variablen am Anfang des innersten Bereichsblocks, damit sie so schnell wie möglich den Bereich verlassen und der Compiler den Speicher optimieren und Ihnen mitteilen kann, wenn Sie sie versehentlich dort verwenden, wo Sie es nicht beabsichtigt hatten.
Im Allgemeinen bevorzuge ich die erste Option, da ich finde, dass die anderen mich oft zwingen, den Code für die Deklarationen zu durchsuchen. Das Definieren aller Variablen im Voraus erleichtert auch das Initialisieren und Überwachen über einen Debugger.
Ich deklariere manchmal Variablen innerhalb eines kleineren Bereichsblocks, aber nur aus einem guten Grund, von dem ich nur sehr wenige habe. Ein Beispiel könnte nach a sein
fork()
, um Variablen zu deklarieren, die nur vom untergeordneten Prozess benötigt werden. Für mich ist diese visuelle Anzeige eine hilfreiche Erinnerung an ihren Zweck.quelle
Wie von anderen angemerkt, ist GCC in dieser Hinsicht (und möglicherweise auch andere Compiler, abhängig von den Argumenten, mit denen sie aufgerufen werden) zulässig, selbst wenn sie sich im Modus "C89" befinden, es sei denn, Sie verwenden die Prüfung "pedantisch". 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 für den Compiler als möglicher Fehler verdächtig ist). Wenn Sie Ihren Code also nicht mit einem pedantischen Setup kompilieren können, muss er wahrscheinlich etwas beachtet werden.
C89 erfordert, dass Variablen vor allen anderen Anweisungen in jedem Bereich deklariert werden. Spätere Standards ermöglichen eine näher zu verwendende Deklaration (die sowohl intuitiver als auch effizienter sein kann), insbesondere die gleichzeitige Deklaration und Initialisierung einer Regelungsvariablen in 'for'-Schleifen.
quelle
Wie bereits erwähnt, gibt es hierzu zwei Denkschulen.
1) Deklarieren Sie alles an der Spitze der Funktionen, da das Jahr 1987 ist.
2) Erklären Sie am nächsten zum ersten Gebrauch und im kleinstmöglichen Umfang.
Meine Antwort darauf lautet BEIDES! Lassen Sie mich erklären:
Für lange 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, von denen einige möglicherweise nur ein "i" für eine for-Schleife sind, die genau am Ende steht unten in der Funktion.
Daraus entwickelte ich eine Erklärung zur obersten PTBS und versuchte, Option 2) religiös zu machen.
Ich bin wegen einer Sache zu Option eins zurückgekehrt: kurzen Funktionen. Wenn Ihre Funktionen kurz genug sind, haben Sie nur wenige lokale Variablen. Da die Funktion kurz ist und Sie sie oben in die Funktion einfügen, sind sie immer noch nahe an der ersten Verwendung.
Das Anti-Muster "deklarieren und auf NULL setzen", wenn Sie oben deklarieren möchten, aber einige für die Initialisierung erforderliche Berechnungen nicht durchgeführt haben, wird ebenfalls behoben, da die zu initialisierenden Elemente wahrscheinlich als Argumente empfangen werden.
Jetzt denke ich also, dass Sie an der Spitze der Funktionen deklarieren und so nah wie möglich an der ersten Verwendung sein sollten. Also BEIDE! Und der Weg, dies zu tun, führt über gut unterteilte Unterprogramme.
Wenn Sie jedoch an einer langen Funktion arbeiten, sollten Sie die Dinge am nächsten verwenden, da dies das Extrahieren von Methoden erleichtert.
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 die Deklaration kurz vor dem Kompilierungsfehler. Das ist die erste Verwendung. Tun Sie dies für alle lokalen Variablen.
Definieren Sie nun einen Bereichsblock, der vor der Deklaration beginnt, und verschieben Sie das Ende, bis das Programm kompiliert wird
Dies wird nicht kompiliert, da es mehr Code gibt, der foo verwendet. Wir können feststellen, dass der Compiler den Code durchgehen konnte, der bar verwendet, weil er nicht foo verwendet. Zu diesem Zeitpunkt gibt es zwei Möglichkeiten. Die mechanische besteht darin, das "}" einfach 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:
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, die zu beachten ist, ist, dass der Wert von foo zwischen den Codeblöcken, die es verwenden, beibehalten werden muss, oder könnte es in beiden nur ein anderes foo sein. Beispielsweise
Diese Situationen erfordern mehr als mein Verfahren. 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 einfach wieder über die erste Verwendung zu setzen. Wenn sich diese erste Verwendung in einer if-Anweisung befindet, legen Sie sie dort ab 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 fertig ist, ist es einfacher zu analysieren, wo sich die Daten befinden. Wenn eine Variable in einem großen Bereichsblock verwendet wird, analysieren Sie die Situation und prüfen Sie, ob Sie dieselbe Variable nur für zwei verschiedene Dinge verwenden (z. B. ein "i", das für zwei for-Schleifen verwendet wird). Wenn die Verwendungen nicht zusammenhängen, erstellen Sie für jede dieser nicht verwendeten Verwendungen neue Variablen.
quelle
Sie sollten alle Variablen oben oder "lokal" in der Funktion deklarieren. Die Antwort ist:
Es hängt davon ab, welche Art von System Sie verwenden:
1 / Eingebettetes System (insbesondere in Bezug auf Leben wie Flugzeug oder Auto): Es ermöglicht die Verwendung des dynamischen Speichers (z. B. Calloc, Malloc, Neu ...). Stellen Sie sich vor, Sie arbeiten in einem sehr großen Projekt mit 1000 Ingenieuren. Was ist, wenn sie neuen dynamischen Speicher zuweisen und vergessen, ihn zu entfernen (wenn er nicht mehr verwendet wird)? Wenn das eingebettete System längere Zeit ausgeführt wird, kommt es zu einem Stapelüberlauf und die Software wird beschädigt. Es ist nicht einfach, die Qualität sicherzustellen (der beste Weg ist, dynamischen Speicher zu sperren).
Wenn ein Flugzeug innerhalb von 30 Tagen läuft und nicht abschaltet, was passiert, wenn die Software beschädigt ist (wenn das Flugzeug noch in der Luft ist)?
2 / Die anderen Systeme wie Web, PC (haben großen Speicherplatz):
Sie sollten die Variable "lokal" deklarieren, um den Speicher mithilfe von zu optimieren. Wenn diese Systeme längere Zeit laufen und ein Stapelüberlauf auftritt (weil jemand vergessen hat, den dynamischen Speicher zu entfernen). Machen Sie einfach das Einfache, um den PC zurückzusetzen: P Es hat keine Auswirkungen auf das Leben
quelle
malloc()
. Obwohl ich noch nie ein Gerät gesehen habe, das dazu nicht in der Lage ist, empfiehlt es sich, eine dynamische Zuordnung auf eingebetteten Systemen zu vermeiden ( siehe hier ). Das hat aber nichts damit zu tun, wo Sie Ihre Variablen in einer Funktion deklarieren.sub rsp, 1008
Speicherplatz für das gesamte Array außerhalb der if-Anweisung zugewiesen. Dies gilt fürclang
undgcc
auf jeder Version und Optimierungsstufe, die ich ausprobiert habe.Ich werde einige Aussagen aus dem Handbuch für gcc Version 4.7.0 für eine klare Erklärung zitieren.
"Der Compiler kann mehrere Basisstandards wie 'c90' oder 'c ++ 98' und GNU-Dialekte dieser Standards wie 'gnu90' oder 'gnu ++ 98' akzeptieren. Durch Angabe eines Basisstandards kann der Compiler akzeptiert alle Programme, die diesem Standard folgen, und solche, die GNU-Erweiterungen verwenden, die ihm nicht widersprechen. Beispielsweise deaktiviert '-std = c90' bestimmte Funktionen von GCC, die mit ISO C90 nicht kompatibel sind, wie z. B. asm und typeof keywords, jedoch nicht andere GNU-Erweiterungen, die in ISO C90 keine Bedeutung haben, z. B. das Weglassen des Mittelbegriffs eines ?: Ausdrucks. "
Ich denke, der entscheidende Punkt Ihrer Frage ist, warum gcc nicht mit C89 übereinstimmt, selbst wenn die Option "-std = c89" verwendet wird. Ich kenne die Version Ihres gcc nicht, aber ich denke, dass es keinen großen Unterschied geben wird. Der Entwickler von gcc hat uns mitgeteilt, dass die Option "-std = c89" nur bedeutet, dass die Erweiterungen, die C89 widersprechen, deaktiviert sind. Es hat also nichts mit einigen Erweiterungen zu tun, die in C89 keine Bedeutung haben. Und die Erweiterung, die die Platzierung der Variablendeklaration nicht einschränkt, gehört zu den Erweiterungen, die C89 nicht widersprechen.
Um ehrlich zu sein, wird jeder denken, dass es C89 auf den ersten Blick der Option "-std = c89" vollständig entsprechen sollte. Aber das tut es nicht. Das Problem, dass alle Variablen am Anfang besser oder schlechter deklariert werden, ist nur Gewohnheitssache.
quelle