Warum erlaubt gcc die Übergabe von Argumenten an eine Funktion ohne Argumente?

75

Ich verstehe nicht, warum dieser Code kompiliert wird.

gcc warnt nicht einmal davor, dass ich zu viele Argumente an foo () übergebe. Ist das erwartetes Verhalten?

roter Drache
quelle
21
Willkommen in der C-Sprache, dies ist Teil des Legacy-Standards. Zu Ihrer Information, es wird in C ++ nicht unterstützt.
Steve-o
9
Sie können den Compiler normalerweise dazu inspirieren, eine Warnung zu generieren, wenn Sie die Funktion alsvoid foo(void)
Hans Passant
6
@HansPassant: Wenn Sie es als deklarieren void foo(void), sollte der Compiler normalerweise einen Fehler und nicht nur eine Warnung ausgeben (obwohl der offizielle Wortlaut lediglich eine "Diagnose" erfordert).
Jerry Coffin
3
add -Wall, wenn Sie es kompilieren
pyCthon
1
@reddragon siehe meine Antwort .
Ecatmur

Antworten:

85

In C akzeptiert eine mit einer leeren Parameterliste deklarierte Funktion beim Aufruf eine beliebige Anzahl von Argumenten, die den üblichen arithmetischen Heraufstufungen unterliegen. Es liegt in der Verantwortung des Aufrufers, sicherzustellen, dass die angegebenen Argumente für die Definition der Funktion geeignet sind.

Um eine Funktion mit Nullargumenten zu deklarieren, müssen Sie schreiben void foo(void);.

Dies ist aus historischen Gründen; Ursprünglich hatten C-Funktionen keine Prototypen, da C aus B , einer typenlosen Sprache, hervorging. Beim Hinzufügen von Prototypen wurden die ursprünglichen typenlosen Deklarationen aus Gründen der Abwärtskompatibilität in der Sprache belassen.

Verwenden Sie -Wstrict-prototypes: gcc, um vor leeren Parameterlisten zu warnen :

Warnen, wenn eine Funktion deklariert oder definiert wird, ohne die Argumenttypen anzugeben. (Eine Funktionsdefinition alten Stils ist ohne Warnung zulässig, wenn eine Deklaration vorangestellt ist, in der die Argumenttypen angegeben sind.)

ecatmur
quelle
53

Aus alten Gründen bedeutet das Deklarieren einer Funktion mit ()für eine Parameterliste im Wesentlichen „die Parameter herausfinden, wenn die Funktion aufgerufen wird“. Verwenden Sie, um anzugeben, dass eine Funktion keine Parameter hat (void).

Bearbeiten: Ich habe das Gefühl, dass ich in diesem Problem den Ruf habe, alt zu sein. Damit Ihre Kinder wissen, wie das Programmieren früher war, ist hier mein erstes Programm . (Nicht C; es zeigt Ihnen, womit wir vorher arbeiten mussten.)

Eric Postpischil
quelle
2
Warten Sie, meinen Sie das ernst? Warum würdest du das jemals tun?
Drise
5
Typregeln, Objektorientierung, Kapselung, Überladung und andere moderne Sprachfunktionen wurden nicht über Nacht entwickelt. Vor Jahrzehnten waren Programmiersprachen viel primitiver. Und experimentell. C war ein Fortschritt gegenüber dem, was davor war, und die Gründe für die starke Typisierung von Funktionsparametern oder wie Sie sie implementieren würden, waren nicht klar. Eine Hauptmotivation zu dieser Zeit war es, Programmierern einfache Möglichkeiten zu geben, mächtige Dinge zu tun. Die Notwendigkeit, starkes Tippen zu verwenden, um Fehler zu reduzieren, war damals keine so große Motivation.
Eric Postpischil
4
@Drise: würdest du nicht mehr. Vor dem Standard von 1989 unterstützte die Sprache C jedoch keine Prototypdeklarationen (wobei die Anzahl und der Typ der Parameter deklariert sind). Sie können den Rückgabetyp der Funktion nur in einer Deklaration angeben. Es gibt eine Menge Legacy-Code, der kaputt gehen würde, wenn Sie die Regeln für leere Parameterlisten in Deklarationen ändern würden. Daher wird er weiterhin unterstützt, aber neuer Code sollte beim Deklarieren von Funktionen immer die Prototypsyntax verwenden.
John Bode
3
In Bezug auf die Typensicherheit war frühes C nur ein Fortschritt gegenüber B. Es gab viele sicher typisierte Sprachen (Simula, Pascal, sogar Algol), die kein Problem damit hatten, eine starke Typisierung von Funktionsparametern durchzusetzen. C gewann, indem es effizienter und einfacher auf ressourcenbeschränkten Minicomputern zu implementieren war, aber die damit verbundenen Kompromisse waren zu dieser Zeit offensichtlich.
Ecatmur
1
@ John Bode hat Recht vor C. 1989 C. IIRC wird auch als traditionelles C bezeichnet. Ein weiteres großartiges 'Feature', das die Verwendung von int für char erlaubt. Mein erster C-Compiler war traditionelles C und es brachte mir bei, ANSI C zu lieben
jqa
10

In C verletzt dieser Code keine Einschränkung (dies würde der Fall sein, wenn er in seiner Prototypform mit definiert wurde void foo(void) {/*...*/}). Da keine Einschränkung der Einschränkung vorliegt , muss der Compiler keine Diagnose ausstellen.

Dieses Programm hat jedoch ein undefiniertes Verhalten gemäß den folgenden C-Regeln:

Von:

(C99, 6.9.1p7) "Wenn der Deklarator eine Parametertypliste enthält, gibt die Liste auch die Typen aller Parameter an. 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) sind die Arten der Parameter in einer folgenden Deklarationsliste zu deklarieren. "

Die fooFunktion bietet keinen Prototyp.

Von:

(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 der Anzahl der Parameter entspricht, ist das Verhalten undefiniert."

Der foo(str)Funktionsaufruf ist undefiniertes Verhalten.

C schreibt der Implementierung nicht vor, eine Diagnose für ein Programm auszustellen, das undefiniertes Verhalten aufruft, aber Ihr Programm ist immer noch ein fehlerhaftes Programm.

ouah
quelle
6

Sowohl der C99-Standard (6.7.5.3) als auch der C11-Standard (6.7.6.3) geben Folgendes an:

Eine Bezeichnerliste deklariert nur die Bezeichner der Parameter der Funktion. Eine leere Liste in einem Funktionsdeklarator, die Teil einer Definition dieser Funktion ist, gibt an, dass die Funktion keine Parameter hat. Die leere Liste in einem Funktionsdeklarator, die nicht Teil einer Definition dieser Funktion ist, gibt an, dass keine Informationen über die Anzahl oder den Typ der Parameter angegeben 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. Wie unten beschrieben, gibt es in C unterschiedliche Grade von "falsch", und Compiler können sich darin unterscheiden, wie sie mit bestimmten Arten von "falsch" umgehen.

Um ein etwas einfacheres Beispiel zu nennen, betrachten Sie das folgende Programm:

Wenn ich das oben genannte mit Clang kompiliere:

Wenn ich mit gcc 4.8 kompiliere, erhalte ich auch mit -Wall keine Fehler oder Warnungen. In einer früheren Antwort wurde die Verwendung von -Wstrict-Prototypen vorgeschlagen, die korrekt angeben, dass die Definition von f nicht in Prototypform vorliegt, aber dies ist wirklich nicht der Punkt. Die C-Standards erlauben eine Funktionsdefinition in einer Nicht-Prototyp-Form wie der oben genannten, und die Standards geben eindeutig an, dass diese Definition angibt, dass die Funktion 0 Argumente akzeptiert.

Jetzt gibt es eine Einschränkung (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 jedoch in diesem Fall nicht, da der Funktionstyp keinen Prototyp enthält. Aber hier ist eine nachfolgende Aussage im Abschnitt Semantik (keine "Einschränkung"), die gilt:

Wenn der Ausdruck, der die aufgerufene Funktion bezeichnet, einen Typ hat, der keinen Prototyp enthält ... Wenn die Anzahl der Argumente nicht der Anzahl der Parameter entspricht, ist das Verhalten undefiniert.

Daher führt der Funktionsaufruf zu undefiniertem Verhalten (dh das Programm ist nicht "streng konform"). Der Standard erfordert jedoch nur dann eine Implementierung, um eine Diagnosemeldung zu melden, wenn eine tatsächliche Einschränkung verletzt wird. In diesem Fall liegt keine Verletzung einer Einschränkung vor. Daher muss gcc keinen Fehler oder keine Warnung melden, um eine "konforme Implementierung" zu sein.

Daher denke ich, dass die Antwort auf die Frage, warum gcc dies zulässt, darin besteht, dass gcc nicht verpflichtet ist, etwas zu melden, da dies keine Einschränkung darstellt. Darüber hinaus behauptet gcc nicht, jede Art von undefiniertem Verhalten zu melden, selbst bei -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 entschieden, es ohne Warnungen zu kompilieren (und anscheinend ignoriert es das Argument einfach).

Steve Siegel
quelle
In den Tagen vor C89 ignorierten die meisten C-Compiler nicht verwendete Argumente und ermöglichten es den Aufrufern, Argumente wegzulassen, die nicht verwendeten Parametern zugeordnet waren. Die Validierung korrekter Aufrufkonventionen ist im Allgemeinen nützlich, aber einige vorhandene APIs erfordern möglicherweise, dass Funktionen mit unterschiedlicher Anzahl und Art von Argumenten übergeben werden. Die normale Handhabung mit ..., aber Parameterwerte für ...Funktionen werden anders abgerufen als für "normale" Funktionen.
Supercat