Warum erlaubt gcc, dass Argumente an eine Funktion übergeben werden, die so definiert ist, dass sie keine Argumente enthält?

Lesezeit: 7 Minuten

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?

  • 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

Benutzeravatar von ecatmur
Ekatmur

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

Benutzeravatar von Eric Postpischil
Eric Postpischil

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

  • Warte, meinst du das ernst? Warum würdest du das jemals tun?

    – Drise

    28. September 2012 um 15:55 Uhr


  • 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 kam, und die Gründe für die starke Typisierung von Funktionsparametern oder wie Sie sie implementieren würden, waren nicht klar. Eine Hauptmotivation war damals, Programmierern einfache Möglichkeiten zu geben, mächtige Dinge zu tun. Die Notwendigkeit, starke Typisierung zu verwenden, um Fehler zu reduzieren, war damals keine so große Motivation.

    – Eric Postpischil

    28. September 2012 um 15:59 Uhr

  • @Drise: würdest du nicht, nicht mehr. Vor dem Standard von 1989 unterstützte die C-Sprache jedoch keine Prototyp-Deklarationen (wo die Anzahl und Typen der Parameter deklariert werden); Sie könnten nur den Rückgabetyp der Funktion in einer Deklaration angeben. Da ist ein viel von Legacy-Code da draußen, der kaputt gehen würde, wenn Sie die Regeln für leere Parameterlisten in Deklarationen ändern würden, also wird es immer noch unterstützt, aber neuer Code sollte es stets Verwenden Sie die Prototyp-Syntax, wenn Sie Funktionen deklarieren.

    – Johannes Bode

    28. September 2012 um 16:12 Uhr


  • In Bezug auf Typsicherheit war das frühe C nur gegenüber B ein Fortschritt. Es gab viele sicher typisierte Sprachen (Simula, Pascal, sogar Algol), die kein Problem damit hatten, eine starke Typisierung von Funktionsparametern durchzusetzen. C gewann, weil es effizienter und einfacher auf ressourcenbeschränkten Minicomputern zu implementieren war, aber die damit verbundenen Kompromisse waren zu diesem Zeitpunkt offensichtlich.

    – ekatmur

    28. September 2012 um 17:26 Uhr

  • @ John Bode hat Recht mit C vor 1989. IIRC wird auch als traditionelles C bezeichnet. Ein weiteres großartiges “Feature”, das die Verwendung von int für char ermöglicht. Mein erster C-Compiler war traditionelles C und hat mich gelehrt, ANSI C zu lieben.

    – jqa

    28. September 2012 um 17:33 Uhr

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.

Benutzeravatar von Steve Siegel
Steve Siegel

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

1417420cookie-checkWarum erlaubt gcc, dass Argumente an eine Funktion übergeben werden, die so definiert ist, dass sie keine Argumente enthält?

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

Privacy policy