Ich verstehe nicht, warum dieser Code kompiliert wird?
#include <stdio.h>
void foo() {
printf("Hello\n");
}
int main() {
const char *str = "bar";
foo(str);
return 0;
}
gcc gibt nicht einmal eine Warnung aus, dass ich zu viele Argumente an foo() übergebe. Ist das erwartetes Verhalten?
In C akzeptiert eine mit leerer Parameterliste deklarierte Funktion beim Aufruf beliebig viele Argumente, die den üblichen arithmetischen Beförderungen unterliegen. Es liegt in der Verantwortung des Aufrufers sicherzustellen, dass die gelieferten Argumente für die Definition der Funktion geeignet sind.
Um eine Funktion mit null Argumenten zu deklarieren, müssen Sie schreiben void foo(void);
.
Dies hat historische Gründe; Ursprünglich hatten C-Funktionen keine Prototypen, aus denen sich C entwickelt hat B, eine typlose Sprache. Als Prototypen hinzugefügt wurden, wurden die ursprünglichen typlosen Deklarationen aus Gründen der Abwärtskompatibilität in der Sprache belassen.
Um gcc dazu zu bringen, vor leeren Parameterlisten zu warnen, verwenden Sie -Wstrict-prototypes
:
Warnt, wenn eine Funktion deklariert oder definiert wird, ohne die Argumenttypen anzugeben. (Eine Funktionsdefinition im alten Stil ist ohne Warnung zulässig, wenn ihr eine Deklaration vorangeht, die die Argumenttypen angibt.)
Aus Legacy-Gründen wird eine Funktion mit deklariert ()
für eine Parameterliste bedeutet im Wesentlichen „die Parameter herausfinden, wenn die Funktion aufgerufen wird“. Um anzugeben, dass eine Funktion keine Parameter hat, verwenden Sie (void)
.
Bearbeiten: Ich habe das Gefühl, dass ich in diesem Problem den Ruf aufnehme, alt zu sein. Nur damit ihr Kinder wisst, wie Programmieren früher war, hier ist es Mein erstes Programm. (Nicht C; es zeigt Ihnen, womit wir vorher arbeiten mussten.)
void foo() {
printf("Hello\n");
}
foo(str);
In C verstößt dieser Code nicht gegen eine Einschränkung (das würde er tun, wenn er in seiner Prototypform mit definiert wäre void foo(void) {/*...*/}
) und da keine Einschränkungsverletzung vorliegt, muss der Compiler keine Diagnose ausgeben.
Dieses Programm hat jedoch ein undefiniertes Verhalten gemäß den folgenden C-Regeln:
Aus:
(C99, 6.9.1p7) “Wenn der Deklarator eine Parametertypliste enthält, spezifiziert die Liste auch die Typen aller Parameter; ein solcher Deklarator dient auch als Funktionsprototyp für spätere Aufrufe derselben Funktion in derselben Übersetzungseinheit. Wenn der Deklarator eine Bezeichnerliste enthält,142) müssen die Typen der Parameter in einer folgenden Deklarationsliste deklariert werden.”
das foo
Funktion stellt keinen Prototyp bereit.
Aus:
(C99, 6.5.2.2p6) “Wenn der Ausdruck, der die aufgerufene Funktion bezeichnet, einen Typ hat, der keinen Prototyp enthält […] Wenn die Anzahl der Argumente nicht gleich der Anzahl der Parameter ist, ist das Verhalten undefiniert.”
das foo(str)
Funktionsaufruf ist undefiniertes Verhalten.
C beauftragt die Implementierung nicht, eine Diagnose für ein Programm auszugeben, das undefiniertes Verhalten aufruft, aber Ihr Programm ist immer noch ein fehlerhaftes Programm.
Sowohl der C99-Standard (6.7.5.3) als auch der C11-Standard (6.7.6.3) besagen:
Eine Bezeichnerliste deklariert nur die Bezeichner der Parameter der Funktion. Eine leere Liste in einem Funktionsdeklarator, der Teil einer Definition dieser Funktion ist, gibt an, dass die Funktion keine Parameter hat. Die leere Liste in einem Funktionsdeklarator, der nicht Teil einer Definition dieser Funktion ist, gibt an, dass keine Informationen über die Anzahl oder Typen der Parameter bereitgestellt werden.
Da die Deklaration von foo Teil einer Definition ist, gibt die Deklaration an, dass foo 0 Argumente akzeptiert, sodass der Aufruf foo(str) zumindest moralisch falsch ist. Aber wie unten beschrieben, gibt es in C verschiedene Grade von “Falsch”, und Compiler können sich darin unterscheiden, wie sie mit bestimmten Arten von “Falsch” umgehen.
Um ein etwas einfacheres Beispiel zu nehmen, betrachten Sie das folgende Programm:
int f() { return 9; }
int main() {
return f(1);
}
Wenn ich das obige mit Clang kompiliere:
tmp$ cc tmp3.c
tmp3.c:4:13: warning: too many arguments in call to 'f'
return f(1);
~ ^
1 warning generated.
Wenn ich mit gcc 4.8 kompiliere, bekomme ich keine Fehler oder Warnungen, auch nicht mit -Wall. Eine frühere Antwort schlug die Verwendung von -Wstrict-prototypes vor, die korrekt meldet, dass die Definition von f nicht in Prototypform vorliegt, aber das ist wirklich nicht der Punkt. Der/die C-Standard(s) erlauben eine Funktionsdefinition in einer Nicht-Prototyp-Form wie der obigen, und die Standards geben eindeutig an, dass diese Definition spezifiziert, dass die Funktion 0 Argumente akzeptiert.
Jetzt gibt es eine Zwang (C11 Abschnitt 6.5.2.2):
Wenn der Ausdruck, der die aufgerufene Funktion bezeichnet, einen Typ hat, der einen Prototyp enthält, muss die Anzahl der Argumente mit der Anzahl der Parameter übereinstimmen.
Diese Einschränkung gilt in diesem Fall jedoch nicht, da der Typ der Funktion keinen Prototyp enthält. Aber hier ist eine nachfolgende Aussage im Semantikabschnitt (keine “Einschränkung”), die zutrifft:
Wenn der Ausdruck, der die aufgerufene Funktion bezeichnet, einen Typ hat, der keinen Prototyp enthält … Wenn die Anzahl der Argumente nicht gleich der Anzahl der Parameter ist, ist das Verhalten undefiniert.
Daher führt der Funktionsaufruf zu undefiniertem Verhalten (dh das Programm ist nicht “streng konform”). Der Standard verlangt jedoch nur, dass eine Implementierung eine Diagnosenachricht meldet, wenn eine tatsächliche Einschränkung verletzt wird, und in diesem Fall liegt keine Verletzung einer Einschränkung vor. Daher ist gcc nicht verpflichtet, einen Fehler oder eine Warnung zu melden, um eine “konforme Implementierung” zu sein.
Ich denke also, die Antwort auf die Frage, warum gcc dies zulässt, lautet, dass gcc nicht verpflichtet ist, irgendetwas zu melden, da dies keine Verletzung einer Einschränkung ist. Außerdem erhebt gcc nicht den Anspruch, jede Art von undefiniertem Verhalten zu melden, auch nicht mit -Wall oder -Wpedantic. Es ist ein undefiniertes Verhalten, was bedeutet, dass die Implementierung wählen kann, wie damit umgegangen werden soll, und gcc hat sich dafür entschieden, es ohne Warnungen zu kompilieren (und anscheinend ignoriert es einfach das Argument).
Willkommen in der C-Sprache, dies ist Teil des Legacy-Standards. FYI es wird in C++ nicht unterstützt.
– Steve-o
28. September 2012 um 15:50 Uhr
Sie können den Compiler normalerweise dazu anregen, eine Warnung zu generieren, wenn Sie die Funktion als deklarieren
void foo(void)
– Hans Passant
28. September 2012 um 15:53 Uhr
@HansPassant: Wenn du es so deklarierst
void foo(void)
sollte der Compiler normalerweise einen Fehler ausgeben, nicht nur eine Warnung (obwohl die offizielle Formulierung einfach eine “Diagnose” erfordert).– Jerry Sarg
28. September 2012 um 15:57 Uhr
fügen Sie -Wall hinzu, wenn Sie es kompilieren
– pyCthon
28. September 2012 um 16:08 Uhr
@reddragon siehe meine Antwort.
– ekatmur
28. September 2012 um 17:26 Uhr