Ich bin vor langer Zeit in einem Forum über eine interessante Frage gestolpert und möchte die Antwort wissen.
Betrachten Sie die folgende C-Funktion:
f1.c
#include <stdbool.h>
bool f1()
{
int var1 = 1000;
int var2 = 2000;
int var3 = var1 + var2;
return (var3 == 0) ? true : false;
}
Dies sollte false
seitdem immer wieder zurückkehren var3 == 3000
. Die main
Funktion sieht folgendermaßen aus:
Haupt c
#include <stdio.h>
#include <stdbool.h>
int main()
{
printf( f1() == true ? "true\n" : "false\n");
if( f1() )
{
printf("executed\n");
}
return 0;
}
Da f1()
sollte immer zurückkehren false
, würde man erwarten, dass das Programm nur eine falsche auf dem Bildschirm druckt . Aber nach dem Kompilieren und Ausführen es, ausgeführt auch angezeigt wird:
$ gcc main.c f1.c -o test
$ ./test
false
executed
Warum ist das so? Hat dieser Code ein undefiniertes Verhalten?
Hinweis: Ich habe es mit kompiliert gcc (Ubuntu 4.9.2-10ubuntu13) 4.9.2
.
f1()
in dieselbe Datei wie kopierenmain()
, werden Sie etwas seltsam: Während es in C ++ korrekt ist,()
eine leere Parameterliste zu verwenden, wird sie in C für eine Funktion mit einer noch nicht definierten Parameterliste verwendet ( Grundsätzlich wird nach dem)
) eine Parameterliste im K & R-Stil erwartet . Um korrekt C zu sein, sollten Sie Ihren Code in ändernbool f1(void)
.main()
könnte vereinfacht werdenint main() { puts(f1() == true ? "true" : "false"); puts(f1() ? "true" : "false"); return 0; }
- dies würde die Diskrepanz besser zeigen.void
?true
undfalse
in K & R 1st ed., also gab es überhaupt keine solchen Probleme. Es war nur 0 und nicht Null für wahr und falsch. Ist es nicht? Ich weiß nicht, ob zu diesem Zeitpunkt Prototypen verfügbar waren._Bool
Typ und keinen<stdbool.h>
Header.Antworten:
Wie in anderen Antworten erwähnt, besteht das Problem darin, dass Sie
gcc
keine Compiler-Optionen festlegen. Wenn Sie dies tun, wird standardmäßig "gnu90" verwendet, eine nicht standardmäßige Implementierung des alten, zurückgezogenen C90-Standards aus dem Jahr 1990.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, wird standardmäßig
int func ()
(wobei( )
"Parameter akzeptieren" bedeutet) verwendet. Dies ändert die Aufrufkonvention der Funktionfunc
, aber nicht die tatsächliche Funktionsdefinition. Da die Größe vonbool
undint
unterschiedlich sind, ruft Ihr Code beim Aufruf der Funktion ein undefiniertes Verhalten auf.Dieses gefährliche Unsinnverhalten 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 immer noch den alten C-Standard. Es gibt wahrscheinlich keinen Grund, warum Sie Ihren Code als etwas anderes als Standard C kompilieren möchten. Sie müssen GCC daher ausdrücklich mitteilen, dass Ihr Code als moderner C-Code kompiliert werden soll, anstatt als über 25 Jahre alter, nicht standardmäßiger GNU-Mist .
Beheben Sie das Problem, indem Sie Ihr Programm immer wie folgt kompilieren:
-std=c11
weist es an, einen halbherzigen Versuch zu unternehmen, nach dem (aktuellen) C-Standard (informell bekannt als C11) zu kompilieren.-pedantic-errors
fordert es auf, das oben Gesagte von ganzem Herzen zu tun und Compilerfehler zu geben, wenn Sie falschen Code schreiben, der gegen den C-Standard verstößt.-Wall
bedeutet, geben Sie mir einige zusätzliche Warnungen, die gut zu haben sein könnten.-Wextra
bedeutet, geben Sie mir einige andere zusätzliche Warnungen, die gut zu haben sein könnten.quelle
-std=gnu11
ist es viel wahrscheinlicher, dass sie wie erwartet funktionieren, als-std=c11
aufgrund einiger oder aller der folgenden Funktionen: Sie benötigen Bibliotheksfunktionen über C11 hinaus (POSIX, X / Open usw.), die im "gnu" verfügbar sind. erweiterte Modi, jedoch im strengen Konformitätsmodus unterdrückt; Fehler in Systemheadern, die in den erweiterten Modi ausgeblendet 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).-pedantic-errors
ist weniger problematisch als,-Werror
aber beide können und können dazu führen, dass Programme auf Betriebssystemen, die nicht in den Tests des ursprünglichen Autors enthalten sind, nicht kompiliert werden können, selbst wenn kein tatsächliches Problem vorliegt.bool
/ verwenden_Bool
, können Sie Ihren C-Code "C ++ - esque" schreiben, wobei Sie davon ausgehen, dass alle Vergleiche und logischen Operatoren aus historischen Gründen ein " Gefällt mir "bool
in C ++ zurückgeben, obwohl sie ein "int
In C" zurückgeben . Dies hat den großen Vorteil, dass Sie statische Analysewerkzeuge verwenden können, um die Typensicherheit aller dieser Ausdrücke zu überprüfen und alle Arten von Fehlern beim Kompilieren aufzudecken. Es ist auch eine Möglichkeit, Absichten in Form von selbstdokumentierendem Code auszudrücken. Und was noch wichtiger ist, es spart auch ein paar Bytes RAM.f1()
In main.c ist kein Prototyp deklariert , daher ist er implizit definiert alsint f1()
, dh es handelt sich um eine Funktion, die eine unbekannte Anzahl von Argumenten akzeptiert und eine zurückgibtint
.Wenn
int
undbool
unterschiedlich groß sind, führt dies zu undefiniertem Verhalten . Auf meinem Computer sindint
es beispielsweise 4 Bytes undbool
ein Byte. Da die Funktion für die Rückgabe definiert istbool
, legt sie bei der Rückgabe ein Byte auf den Stapel. Da es jedoch implizit deklariert ist ,int
von main.c zurückzukehren, versucht die aufrufende Funktion, 4 Bytes vom Stapel zu lesen.Die Standard-Compileroptionen in gcc sagen Ihnen nicht, dass dies der Fall ist. Wenn Sie jedoch mit kompilieren
-Wall -Wextra
, erhalten Sie Folgendes:Um dies zu beheben, fügen Sie eine Deklaration für
f1
in main.c hinzu, bevormain
:Beachten Sie, dass die Argumentliste explizit auf gesetzt ist
void
, was dem Compiler mitteilt, dass die Funktion keine Argumente akzeptiert, im Gegensatz zu einer leeren Parameterliste, die eine unbekannte Anzahl von Argumenten bedeutet. Die Definitionf1
in f1.c sollte ebenfalls geändert werden, um dies widerzuspiegeln.quelle
-Werror-implicit-function-declaration
, die Optionen von GCC zu erweitern, so dass diese nicht mehr vorbei gleiten. Eine noch bessere Wahl ist es-Werror
, alle Warnungen in Fehler umzuwandeln. Zwingt Sie, alle Warnungen zu korrigieren, wenn sie angezeigt werden.Ich finde es interessant zu sehen, wo die in Lundins hervorragender Antwort erwähnte Größeninkongruenz tatsächlich auftritt.
Wenn Sie mit kompilieren
--save-temps
, erhalten Sie Assembly-Dateien, die Sie anzeigen können. Hier ist der Teil, in demf1()
der== 0
Vergleich durchgeführt und sein Wert zurückgegeben wird:Der zurückkehrende Teil ist
sete %al
. In den x86-Aufrufkonventionen von C werden Rückgabewerte von 4 Byte oder weniger (einschließlichint
undbool
) über das Register zurückgegeben%eax
.%al
ist das niedrigste Byte von%eax
. Die oberen 3 Bytes von%eax
bleiben also in einem unkontrollierten Zustand.Jetzt in
main()
:Diese prüft , ob die ganze von
%eax
Null ist , weil sie es testet einen int denkt.Das Hinzufügen einer expliziten Funktionsdeklaration ändert sich
main()
zu:Welches ist, was wir wollen.
quelle
Bitte kompilieren Sie mit einem Befehl wie diesem:
Ausgabe:
Mit einer solchen Meldung sollten Sie wissen, wie Sie sie korrigieren können.
Bearbeiten: Nachdem ich einen (jetzt gelöschten) Kommentar gelesen hatte, versuchte ich, Ihren Code ohne die Flags zu kompilieren. Nun, dies führte mich zu Linkerfehlern ohne Compilerwarnungen anstelle von Compilerfehlern. Und diese Linker-Fehler sind schwieriger zu verstehen. Selbst wenn dies
-std-gnu99
nicht erforderlich ist, versuchen Sie bitte, sie immer zu verwenden-Wall -Werror
, da dies Ihnen viel Schmerz im Arsch erspart .quelle