Gegebenes (vereinfachtes) Code-Snippet:
void foo(int a, int b); // declaration with prototype
int main(void)
{
foo(1, 5); // type-checked call (i.e. because of previous prototype)
return 0;
}
void foo() // old-style definition (with empty argument list)
{
}
und Befehlszeilenoptionen (obwohl sie, wie ich überprüft habe, nicht wichtig sind):
-x c -std=c11 -pedantic -Wall
gcc 7.2 kann es nicht mit folgender Fehlermeldung kompilieren:
Fehler: Anzahl der Argumente stimmt nicht mit Prototyp überein
während clang 4.0 es ohne Beanstandungen übersetzt.
Welche Implementierung ist nach C-Standard richtig? Ist es gültig, dass die Definition im alten Stil den vorherigen Prototyp “annulliert”?
(C11, 6.7p4 Constraints) „Alle Deklarationen im selben Gültigkeitsbereich, die sich auf dasselbe Objekt oder dieselbe Funktion beziehen, müssen kompatible Typen angeben.“
und
(C11, 6.7.6.3p14) “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. […]”
Meiner Meinung nach wird die Einschränkung von 6.7p4 verletzt und eine Diagnose muss ausgestellt werden.
BEARBEITEN:
Wie von @hvd darauf hingewiesen, ist es eigentlich nicht korrekt. 6.7.6.3p14 bedeutet nicht void foo() {}
bietet einen Prototyp für foo
gem DR#317. In diesem Sinne wird die 6.7p4-Beschränkung nicht verletzt, und so hat clang Recht, sich nicht zu beschweren.
Haftungsausschluss: Ich bin kein Sprachjurist, aber ich spiele einen auf Stackoverflow.
Wenn der Compiler keine Diagnose ausgibt, wäre dies nicht konform und könnte als Fehler angesehen werden, wenn der Compiler behauptet, konform zu sein.
C.2011§6.7.6.3¶14 (Hervorhebung von mir):
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.
Also die Definition von foo
gibt keine Parameter an, während die Deklaration von foo
zuvor zwei Parameter angegeben.
C.2011§6.7.6.3¶15:
Damit zwei Funktionstypen kompatibel sind, müssen beide kompatible Rückgabetypen angeben.146)
Darüber hinaus enthält der Parametertyp Listen, wenn beide vorhanden sind, muss in der Anzahl der Parameter und in der Verwendung des Auslassungszeichens übereinstimmen; entsprechende Parameter müssen kompatible Typen haben.
146) Wenn beide Funktionstypen vom alten Stil sind, werden die Parametertypen nicht verglichen.
Somit sind die beiden Erklärer von foo
sind nicht kompatibel.
Verdammt! Aus dem Kommentar von @hvd:
Das ist allgemein bekannt void foo()
liefert keinen Prototyp, nicht einmal in einer Definition. Es gab einen DR, der dies explizit beantwortete. Die Art von foo
in dieser Definition ist void foo()
nicht void foo(void)
und void foo()
und void foo(int, int)
sind kompatible Typen. Diese Antwort ist falsch.
Der hervorgehobene Teil des obigen Textes aus dem Standard ist die Lücke, die die Uneinigkeit in der Anzahl der Argumente, aber kompatible Typen zulässt. Obwohl die Funktionsdefinition eine Funktion angibt, die keine Argumente akzeptiert, gibt es eigentlich keine Inkompatibilität zwischen dem Typ von, da die Liste der Parametertypen tatsächlich fehlt foo
in seiner Funktion Prototyp und der Art der foo
in der Funktionsdefinition.
Clang 4.0 scheint also recht zu haben, da es keine Einschränkungsverletzung gibt.
Mein ursprüngliches Argument wird ungültig, also bearbeite ich diesen Teil meiner ursprünglichen Antwort.
In Kommentaren haben Sie tatsächlich das folgende Beispiel präsentiert:
void foo () {}
int main () { foo(1, 2); return 0; }
Und gefragt, warum sich der Compiler in diesem Fall nicht beschwert. Dies wird hier eigentlich angesprochen, aber auf den Punkt gebracht: C.2011 akzeptiert immer noch die K&R-C-Funktionsdefinitionssyntax. Also, während void foo() {}
ist eine Definition, die keine Argumente akzeptiert, der Prototyp, der für die Argumentvalidierung verwendet wird, ist derselbe wie void foo();
, da die leere Argumentliste als K&R geparst wird. Die moderne C-Syntax zum Erzwingen einer ordnungsgemäßen Argumentüberprüfung wäre zu verwenden void foo(void) {}
stattdessen.
Aus dem C-Standard (6.7.6.3 Funktionsdeklaratoren (einschließlich Prototypen))
15 Damit zwei Funktionstypen kompatibel sind, müssen beide kompatible Rückgabetypen spezifizieren.146) Darüber hinaus … Wenn ein Typ eine Parametertypliste hat und der andere Typ durch eine Funktionsdefinition spezifiziert wird, die eine (möglicherweise leere) Bezeichnerliste enthält , müssen beide in der Anzahl der Parameter übereinstimmen, und der Typ jedes Prototypparameters muss mit dem Typ kompatibel sein, der sich aus der Anwendung der Standardargumenterhöhungen auf den Typ des entsprechenden Bezeichners ergibt. (Bei der Bestimmung der Typkompatibilität und eines zusammengesetzten Typs wird angenommen, dass jeder Parameter, der mit einem Funktions- oder Array-Typ deklariert ist, den angepassten Typ hat, und jeder mit einem qualifizierten Typ deklarierte Parameter wird so behandelt, als hätte er die nicht qualifizierte Version seines deklarierten Typs.)
Und (6.2.7 Kompatibler Typ und zusammengesetzter Typ)
2 Alle Deklarationen, die sich auf dasselbe Objekt oder dieselbe Funktion beziehen, müssen einen kompatiblen Typ haben; Andernfalls, Das Verhalten ist undefiniert
Daher hat das in der Frage gezeigte Programm ein undefiniertes Verhalten. Der Compiler kann wie GCC eine Diagnosemeldung ausgeben.
Ich habe kein Zitat aus einer Norm (edit: verweisen C11
Kapitel 6.7.6.3/P14), aber nach meinem Verständnis gcc
ist richtig zu schreien, da Sie sich selbst widersprechen.
Du versprochen dass Sie in der Funktionsdefinition in der Deklarationsliste zwei Parameter vom Typ haben int
, aber sie sind nicht da. Im Falle einer Funktionsdefinition bedeutet eine leere Liste, dass die Funktion keinen Parameter annehmen sollte. Es liegt also eine Einschränkungsverletzung vor und gcc
ist berechtigt zu reklamieren.
Es scheint, dass dies ein Problem in ist klirren das erzeugt zumindest keine Warnung.
Zitate:
Kapitel §6.7, P4 (Einschränkungen)
Alle Deklarationen im selben Gültigkeitsbereich, die sich auf dasselbe Objekt oder dieselbe Funktion beziehen, müssen kompatible Typen angeben.
dann Kapitel §6.7.6.3, P14,
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.
Dies stellt also eine Einschränkungsverletzung dar und rechtfertigt die Ausgabe einer Diagnose.
@Grzegorz Szpetkowski Sie sollten sich fragen – was ist der Sinn davon. Warum die UB aufrufen?
– 0___________
29. August 2017 um 18:31 Uhr
@ PeterJ_01: Es kann nützlich sein, dies für die Arbeit mit Legacy-Code zu wissen, der überraschenderweise auf verschiedenen Compilern kompiliert wird oder nicht.
– Grzegorz Szpetkowski
29. August 2017 um 18:34 Uhr
@Grzegorz Szpetkowski Ein solcher Code muss auf die richtige Weise umgeschrieben werden
– 0___________
29. August 2017 um 18:35 Uhr
@PeterJ_01: Die Codebasis kann groß sein und manchmal ist es nicht praktikabel (in Bezug auf den Geschäftswert und die Mitarbeitermonate), sie neu zu schreiben, insbesondere wenn sie bereits für Kunden funktioniert. Dies gilt insbesondere für unternehmenskritische Software wie Software für U-Boote oder Kernkraftwerke.
– Grzegorz Szpetkowski
29. August 2017 um 18:44 Uhr