Warum liefern nicht übereinstimmender Prototyp und Definition mit leerer Argumentliste unterschiedliche Ergebnisse in GCC und Clang?

Lesezeit: 8 Minuten

Benutzer-Avatar
Grzegorz Szpetkowski

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”?

  • @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


Benutzer-Avatar
ouah

(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.

  • 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.

    Benutzer743382

    30. August 2017 um 0:15 Uhr


  • @hvd: Außer es gibt oben eine Deklaration mit Prototyp main zum void foo(int, int). Es ist die Definition von foo das ergibt eine leere Argumentliste. Bedeutet dies nicht, dass Prototyp und Definition nicht übereinstimmen?

    – jxh

    30. August 2017 um 0:24 Uhr

  • @jxh Ja, der Prototyp und die Definition stimmen tatsächlich nicht überein. Es würde mich nicht überraschen, wenn das bedeutet, dass der Code undefiniertes Verhalten hat. Aber das sagt diese Antwort nicht aus. Diese Antwort besagt, dass die Definition a angibt Typ nicht mit der früheren Deklaration kompatibel, weshalb eine Einschränkung verletzt wird. Der Typ ist aber kompatibel.

    Benutzer743382

    30. August 2017 um 0:28 Uhr

  • @ GrzegorzSzpetkowski Was jxh gesagt hat. Versuchen void foo(int a, int b); void foo();. Aber eigentlich werde ich langsam unsicher. Obwohl 6.7.6.3p15 ein „soll“ außerhalb einer Einschränkung hat, beginnt es mit „Damit zwei Funktionstypen kompatibel sind, müssen beide kompatible Rückgabetypen angeben. Außerdem …“, bevor es zu Ihrem „beide müssen übereinstimmen in der Anzahl der Parameter”, und es könnte bedeuten, dass es dies obwohl implizieren soll void foo(); und void foo() {} beide geben foo Typ void()nur ersteres ist kompatibel mit void foo(int, int);. Die ursprüngliche Antwort doch richtig machen.

    Benutzer743382

    30. August 2017 um 18:52 Uhr


  • @GrzegorzSzpetkowski: Mir ist nicht klar, dass die Gesamtheit von 6.7.6.3/15 für die Kompatibilität erforderlich ist. Man konnte nur die ersten beiden Sätze lesen, die von Kompatibilität sprechen. Der Rest des Absatzes ist lediglich mehr Semantik. Aber ich bin immer noch kein Sprachanwalt.

    – jxh

    30. August 2017 um 19:11 Uhr

Benutzer-Avatar
jxh

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.

  • C99 hatte denselben Wortlaut (in 6.7.5.3/14).

    – Melpomen

    29. August 2017 um 18:24 Uhr

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.

  • und einige Kommentare – dieses Thema ist seit 15 Jahren live online, wahrscheinlich ist das nichtig foo(int) and then void foo(){...} ist richtig und void foo(double)` und dann void foo(){…}` ist falsch. Aber ich bin nicht der Sprachanwalt

    – 0___________

    29. August 2017 um 18:26 Uhr


  • Undefiniertes Verhalten erfordert per se keine Diagnose, daher würde dies bedeuten, dass Clang korrekt ist.

    – au

    29. August 2017 um 18:26 Uhr

  • @ouah: Es würde bedeuten, dass Clang nicht falsch ist. Es wäre freundlicher, wenn es eine Diagnose ausgeben würde.

    – Keith Thompson

    29. August 2017 um 18:29 Uhr

  • @ PeterJ_01: Ich bin mir ziemlich sicher, dass beide falsch sind (undefiniertes Verhalten).

    – Keith Thompson

    29. August 2017 um 18:30 Uhr

  • Souravs Verweis auf 6.7 P4 ist spezifischer (Deklarationen im gleichen Umfang) und daher zählt dieser. Der Code hat eine Einschränkungsverletzung und somit eine Diagnose muss emittiert werden.

    – Jens Gustedt

    29. August 2017 um 19:01 Uhr

Benutzer-Avatar
Sourav Ghosh

Ich habe kein Zitat aus einer Norm (edit: verweisen C11Kapitel 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.

1132660cookie-checkWarum liefern nicht übereinstimmender Prototyp und Definition mit leerer Argumentliste unterschiedliche Ergebnisse in GCC und Clang?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy