Seit f1() sollte immer wiederkommen falsewürde man erwarten, dass das Programm nur eine ausgibt FALSCH zum Bildschirm. Aber nach dem Kompilieren und Ausführen hingerichtet wird auch angezeigt:
$ gcc main.c f1.c -o test
$ ./test
false
executed
Warum ist das so? Hat dieser Code eine Art undefiniertes Verhalten?
Hinweis: Ich habe es mit kompiliert gcc (Ubuntu 4.9.2-10ubuntu13) 4.9.2.
Andere haben erwähnt, dass Sie einen Prototyp benötigen, da sich Ihre Funktionen in separaten Dateien befinden. Aber auch wenn Sie kopiert haben f1() in dieselbe Datei wie main()würden Sie eine gewisse Verrücktheit bekommen: Während es in C++ korrekt ist, es zu verwenden () für eine leere Parameterliste, in C, das für eine Funktion mit einer noch nicht definierten Parameterliste verwendet wird (es erwartet grundsätzlich eine Parameterliste im K&R-Stil nach der )). Um korrekt C zu sein, sollten Sie Ihren Code in ändern bool f1(void).
– Ulizeuge
8. April 2016 um 12:21 Uhr
Das main() vereinfacht werden könnte int main() { puts(f1() == true ? "true" : "false"); puts(f1() ? "true" : "false"); return 0; } – dies würde die Diskrepanz besser zeigen.
– Paläc
10. April 2016 um 12:05 Uhr
@uliwitness Was ist mit K&R 1st ed. (1978), als es keine gab void?
– Ho1
11. April 2016 um 16:39 Uhr
@uliwitness Es gab keine true und false in K&R 1st ed., also gab es solche Probleme überhaupt nicht. Es war nur 0 und ungleich Null für wahr und falsch. Ist es nicht? Ich weiß nicht, ob es damals Prototypen gab.
– Ho1
11. April 2016 um 16:45 Uhr
K&R 1st Edn ging Prototypen (und dem C-Standard) um mehr als ein Jahrzehnt voraus (1978 für das Buch gegenüber 1989 für den Standard) – tatsächlich lag C++ (C mit Klassen) noch in der Zukunft, als K&R1 veröffentlicht wurde. Auch vor C99 gab es keine _Bool Typ und Nr <stdbool.h> Header.
– Jonathan Leffler
13. April 2016 um 6:50 Uhr
Lundin
Wie in anderen Antworten erwähnt, besteht das Problem darin, dass Sie verwenden gcc ohne Compiler-Optionen gesetzt. Wenn Sie dies tun, wird standardmäßig das sogenannte “gnu90” verwendet, das eine nicht standardmäßige Implementierung des alten, zurückgezogenen C90-Standards von 1990 ist.
Im alten C90-Standard gab es einen großen Fehler in der C-Sprache: Wenn Sie vor der Verwendung einer Funktion keinen Prototyp deklariert haben, würde dies standardmäßig der Fall sein int func () (wo ( ) bedeutet “jeden Parameter akzeptieren”). Dadurch ändert sich die Aufrufkonvention der Funktion func, ändert aber nicht die eigentliche Funktionsdefinition. Da die Größe von bool und int unterschiedlich sind, ruft Ihr Code undefiniertes Verhalten auf, wenn die Funktion aufgerufen wird.
Dieses gefährliche Unsinnsverhalten wurde im Jahr 1999 mit der Veröffentlichung des C99-Standards behoben. Implizite Funktionsdeklarationen wurden verboten.
Leider verwendet GCC bis Version 5.xx standardmäßig noch den alten C-Standard. Es gibt wahrscheinlich keinen Grund, warum Sie Ihren Code als etwas anderes als Standard-C kompilieren sollten. Sie müssen GCC also explizit mitteilen, dass es Ihren Code als modernen C-Code kompilieren soll, anstatt als über 25 Jahre alten, nicht standardmäßigen GNU-Mist .
Beheben Sie das Problem, indem Sie Ihr Programm immer wie folgt kompilieren:
gcc -std=c11 -pedantic-errors -Wall -Wextra
-std=c11 weist es an, einen halbherzigen Versuch zu unternehmen, gemäß dem (aktuellen) C-Standard (umgangssprachlich als C11 bekannt) zu kompilieren.
-pedantic-errors weist es an, das Obige von ganzem Herzen zu tun und Compilerfehler zu geben, wenn Sie falschen Code schreiben, der gegen den C-Standard verstößt.
-Wall bedeutet, mir einige zusätzliche Warnungen zu geben, die gut sein könnten.
-Wextra bedeutet, mir einige andere zusätzliche Warnungen zu geben, die gut sein könnten.
Diese Antwort ist insgesamt richtig, jedoch für kompliziertere Programme -std=gnu11 viel wahrscheinlicher als erwartet funktioniert -std=c11, aufgrund einiger oder aller der folgenden: Bibliotheksfunktionalität jenseits von C11 (POSIX, X/Open usw.) erforderlich, die in den erweiterten Modi “gnu” verfügbar ist, aber im strikten Konformitätsmodus unterdrückt wird; Fehler in System-Headern, die in den erweiterten Modi versteckt sind, z. B. unter der Annahme, dass nicht standardmäßige Typedefs verfügbar sind; unbeabsichtigte Verwendung von Trigraphen (diese Standardfehlfunktion ist im “gnu”-Modus deaktiviert).
– zol
7. April 2016 um 14:51 Uhr
Aus ähnlichen Gründen empfehle ich zwar im Allgemeinen die Verwendung hoher Warnstufen, kann aber die Verwendung von Warnungen-sind-Fehler-Modi nicht unterstützen. -pedantic-errors ist weniger lästig als -Werror aber beides kann und kann dazu führen, dass Programme auf Betriebssystemen, die nicht in den Tests des ursprünglichen Autors enthalten sind, nicht kompiliert werden, selbst wenn kein tatsächliches Problem vorliegt.
– zol
7. April 2016 um 14:55 Uhr
@Lundin Im Gegensatz dazu ist das zweite Problem, das ich erwähnt habe (Fehler in den Systemheadern, die durch strenge Konformitätsmodi aufgedeckt werden). allgegenwärtig; Ich habe umfangreiche, systematische Tests durchgeführt, und es gibt sie nein weit verbreitete Betriebssysteme, die nicht mindestens einen solchen Fehler aufweisen (jedenfalls seit zwei Jahren). C-Programme, die nur die Funktionalität von C11 ohne weitere Ergänzungen benötigen, sind meiner Erfahrung nach ebenfalls eher die Ausnahme als die Regel.
– zol
7. April 2016 um 15:10 Uhr
@joop Wenn Sie Standard-C verwenden bool/_Bool dann können Sie Ihren C-Code auf “C++-artige” Weise schreiben, wobei Sie davon ausgehen, dass alle Vergleiche und logischen Operatoren a zurückgeben bool wie in C++, obwohl sie ein zurückgeben int in C, aus historischen Gründen. Dies hat den großen Vorteil, dass Sie statische Analysewerkzeuge verwenden können, um die Typsicherheit all dieser Ausdrücke zu überprüfen und alle Arten von Fehlern zur Kompilierzeit aufzudecken. Es ist auch eine Möglichkeit, Absichten in Form von selbstdokumentierendem Code auszudrücken. Und weniger wichtig, es spart auch ein paar Bytes RAM.
– Ludin
8. April 2016 um 13:47 Uhr
Beachten Sie, dass die meisten neuen Sachen in C99 von diesem über 25 Jahre alten GNU-Mist stammten.
– Shahbaz
10. April 2016 um 16:04 Uhr
dbusch
Sie haben keinen Prototyp deklariert für f1() in main.c, also ist es implizit definiert als int f1()was bedeutet, dass es sich um eine Funktion handelt, die eine unbekannte Anzahl von Argumenten akzeptiert und ein zurückgibt int.
Wenn int und bool unterschiedlich groß sind, wird dies zur Folge haben undefiniertes Verhalten. Auf meiner Maschine z. int ist 4 Byte und bool ist ein Byte. Da ist die Funktion definiert zurückgeben bool, legt es ein Byte auf den Stapel, wenn es zurückkehrt. Da es jedoch implizit deklariert zurückgeben int von main.c versucht die aufrufende Funktion, 4 Bytes aus dem Stack zu lesen.
Die standardmäßigen Compileroptionen in gcc sagen Ihnen nicht, dass dies der Fall ist. Aber wenn Sie mit kompilieren -Wall -Wextradas bekommst du:
main.c: In function ‘main’:
main.c:6: warning: implicit declaration of function ‘f1’
Um dies zu beheben, fügen Sie eine Deklaration für hinzu f1 in main.c, vorher main:
bool f1(void);
Beachten Sie, dass die Argumentliste explizit auf gesetzt ist void, die dem Compiler mitteilt, dass die Funktion keine Argumente akzeptiert, im Gegensatz zu einer leeren Parameterliste, die eine unbekannte Anzahl von Argumenten bedeutet. Die Definition f1 in f1.c sollte ebenfalls geändert werden, um dies widerzuspiegeln.
Etwas, das ich in meinen Projekten gemacht habe (als ich noch GCC benutzte), war das Hinzufügen -Werror-implicit-function-declaration zu den Optionen von GCC, damit diese nicht mehr vorbeirutscht. Eine noch bessere Wahl ist -Werror um alle Warnungen in Fehler umzuwandeln. Zwingt Sie, alle Warnungen zu beheben, wenn sie angezeigt werden.
– Ulizeuge
8. April 2016 um 12:41 Uhr
Sie sollten auch keine leeren Klammern verwenden, da dies eine veraltete Funktion ist. Das heißt, sie können solchen Code in der nächsten Version des C-Standards verbieten.
– Ludin
8. April 2016 um 13:52 Uhr
@uliwitness Ah. Gute Informationen für diejenigen, die von C++ kommen und sich nur in C versuchen.
– Selten ‘Wo ist Monica’ Needy
8. April 2016 um 18:00 Uhr
Der Rückgabewert wird normalerweise nicht auf den Stack, sondern in ein Register gelegt. Siehe Owens Antwort. Außerdem legt man in der Regel nie ein Byte in den Stack, sondern ein Vielfaches der Wortgröße.
– rsanchez
9. April 2016 um 22:00 Uhr
Neuere Versionen von GCC (5.xx) geben diese Warnung ohne die zusätzlichen Flags aus.
– Übersicht
12. April 2016 um 19:20 Uhr
Ich finde es interessant zu sehen, wo die in Lundins hervorragender Antwort erwähnte Größenabweichung tatsächlich auftritt.
Wenn Sie mit kompilieren --save-temps, erhalten Sie Assembly-Dateien, die Sie sich ansehen können. Hier ist der Teil, wo f1() tut das == 0 Vergleich und gibt seinen Wert zurück:
cmpl $0, -4(%rbp)
sete %al
Der zurückkehrende Teil ist sete %al. Geben Sie in den x86-Aufrufkonventionen von C Werte zurück, die 4 Byte oder kleiner sind (einschließlich int und bool) werden über Register zurückgegeben %eax. %al ist das niedrigste Byte von %eax. Also die oberen 3 Bytes von %eax bleiben in einem unkontrollierten Zustand.
Jetzt in main():
call f1
testl %eax, %eax
je .L2
Diese prüft, ob die ganz von %eax ist null, weil es denkt, dass es ein int testet.
Das Hinzufügen einer expliziten Funktionsdeklaration ändert sich main() zu:
call f1
testb %al, %al
je .L2
das wollen wir.
jdarthenay
Bitte kompilieren Sie mit einem Befehl wie diesem:
main.c: In function 'main':
main.c:14:5: error: implicit declaration of function 'f1' [-Werror=impl
icit-function-declaration]
printf( f1() == true ? "true\n" : "false\n");
^
cc1.exe: all warnings being treated as errors
Bei einer solchen Meldung sollten Sie wissen, was zu tun ist, um sie zu korrigieren.
Bearbeiten: Nachdem ich einen (jetzt gelöschten) Kommentar gelesen habe, habe ich versucht, Ihren Code ohne die Flags zu kompilieren. Nun, das führte zu Linker-Fehlern ohne Compiler-Warnungen anstelle von Compiler-Fehlern. Und diese Linker-Fehler sind schwieriger zu verstehen, also selbst wenn -std-gnu99 ist nicht notwendig, versuchen Sie es zumindest immer -Wall -Werror das erspart dir viel Ärger.
14272900cookie-checkDiese C-Funktion sollte immer false zurückgeben, tut es aber nichtyes
Andere haben erwähnt, dass Sie einen Prototyp benötigen, da sich Ihre Funktionen in separaten Dateien befinden. Aber auch wenn Sie kopiert haben
f1()
in dieselbe Datei wiemain()
würden Sie eine gewisse Verrücktheit bekommen: Während es in C++ korrekt ist, es zu verwenden()
für eine leere Parameterliste, in C, das für eine Funktion mit einer noch nicht definierten Parameterliste verwendet wird (es erwartet grundsätzlich eine Parameterliste im K&R-Stil nach der)
). Um korrekt C zu sein, sollten Sie Ihren Code in ändernbool f1(void)
.– Ulizeuge
8. April 2016 um 12:21 Uhr
Das
main()
vereinfacht werden könnteint main() { puts(f1() == true ? "true" : "false"); puts(f1() ? "true" : "false"); return 0; }
– dies würde die Diskrepanz besser zeigen.– Paläc
10. April 2016 um 12:05 Uhr
@uliwitness Was ist mit K&R 1st ed. (1978), als es keine gab
void
?– Ho1
11. April 2016 um 16:39 Uhr
@uliwitness Es gab keine
true
undfalse
in K&R 1st ed., also gab es solche Probleme überhaupt nicht. Es war nur 0 und ungleich Null für wahr und falsch. Ist es nicht? Ich weiß nicht, ob es damals Prototypen gab.– Ho1
11. April 2016 um 16:45 Uhr
K&R 1st Edn ging Prototypen (und dem C-Standard) um mehr als ein Jahrzehnt voraus (1978 für das Buch gegenüber 1989 für den Standard) – tatsächlich lag C++ (C mit Klassen) noch in der Zukunft, als K&R1 veröffentlicht wurde. Auch vor C99 gab es keine
_Bool
Typ und Nr<stdbool.h>
Header.– Jonathan Leffler
13. April 2016 um 6:50 Uhr