Warum wird eine Funktion ohne Parameter (im Vergleich zur tatsächlichen Funktionsdefinition) kompiliert?

388

Ich bin gerade auf den C-Code von jemandem gestoßen, bei dem ich verwirrt bin, warum er kompiliert wird. Es gibt zwei Punkte, die ich nicht verstehe.

Erstens hat der Funktionsprototyp keine Parameter im Vergleich zur tatsächlichen Funktionsdefinition. Zweitens hat der Parameter in der Funktionsdefinition keinen Typ.

#include <stdio.h>

int func();

int func(param)
{
    return param;
}

int main()
{
    int bla = func(10);    
    printf("%d", bla);
}

Warum funktioniert das? Ich habe es in einigen Compilern getestet und es funktioniert gut.

AdmiralJonB
quelle
77
Es ist K & R C. Wir haben in den 1980er Jahren Code wie diesen geschrieben, bevor es Prototypen mit voller Funktion gab.
Hughdbrown
6
gcc warnt mit -Wstrict-prototypessowohl für die int func()und int main(): xc: 3: Warnung: Funktionsdeklaration nicht ein Prototyp. Sie sollten erklären , main()wie main(void)auch.
Jens
3
@Jens Warum hast du die Frage bearbeitet? Sie scheinen den Punkt verpasst zu haben ...
dlras2
1
Ich habe gerade das implizite int explizit gemacht. Wie verfehlt das den Punkt? Ich glaube, der Punkt ist, warum int func();ist kompatibel mit int func(arglist) { ... }.
Jens
3
@MatsPetersson Das ist falsch. C99 5.1.2.2.1 widerspricht ausdrücklich Ihrer Behauptung und besagt, dass die Version ohne Argument ist int main(void).
Jens

Antworten:

271

Alle anderen Antworten sind richtig, aber nur zur Vervollständigung

Eine Funktion wird folgendermaßen deklariert:

  return-type function-name(parameter-list,...) { body... }

Rückgabetyp ist der Variablentyp, den die Funktion zurückgibt. Dies kann kein Array- oder Funktionstyp sein. Wenn nicht angegeben, wird int angenommen .

Funktionsname ist der Name der Funktion.

Parameterliste ist die Liste der Parameter, die die Funktion durch Kommas getrennt nimmt. Wenn keine Parameter angegeben sind, nimmt die Funktion keine an und sollte mit einem leeren Satz in Klammern oder mit dem Schlüsselwort void definiert werden. Befindet sich in der Parameterliste kein Variablentyp vor einer Variablen, wird int angenommen . Arrays und Funktionen werden nicht an Funktionen übergeben, sondern automatisch in Zeiger konvertiert. Wenn die Liste mit einem Auslassungszeichen (, ...) abgeschlossen wird, ist keine Anzahl von Parametern festgelegt. Hinweis: Der Header stdarg.h kann verwendet werden, um auf Argumente zuzugreifen, wenn eine Ellipse verwendet wird.

Und noch einmal der Vollständigkeit halber. Aus C11-Spezifikation 6: 11: 6 (Seite: 179)

Die Verwendung von Funktionsdeklaratoren mit leeren Klammern (keine Parameterdeklaratoren im Prototypformat) ist eine veraltete Funktion .

Krishnabhadra
quelle
2
"Wenn sich in der Parameterliste kein Variablentyp vor einer Variablen befindet, wird int angenommen." Ich sehe dies in Ihrem bereitgestellten Link, aber ich kann es in keinem Standard c89, c99 finden ... Können Sie eine andere Quelle angeben?
Godaygo
"Wenn keine Parameter angegeben werden, nimmt die Funktion keine an und sollte mit einem leeren Satz von Klammern definiert werden" klingt im Widerspruch zur Antwort von Tony The Lion, aber ich habe gerade erfahren, dass die Antwort von Tony The Lion auf die harte Tour richtig ist.
Jakun
160

In C func()bedeutet, dass Sie eine beliebige Anzahl von Argumenten übergeben können. Wenn Sie keine Argumente wollen, müssen Sie als deklarieren func(void). Der Typ, den Sie an Ihre Funktion übergeben, wird standardmäßig verwendet, wenn nichts angegeben ist int.

Tony der Löwe
quelle
3
Tatsächlich gibt es keinen einzigen Typ, der standardmäßig int ist. Tatsächlich sind alle Argumente standardmäßig int (sogar der Rückgabetyp). Es ist vollkommen in Ordnung anzurufen func(42,0x42);(wobei alle Anrufe das Formular mit zwei Argumenten verwenden müssen).
Jens
1
In C ist es durchaus üblich, dass nicht angegebene Typen standardmäßig int verwenden, z. B. wenn Sie eine Variable deklarieren: unsigned x; Welcher Typ ist die Variable x? Es stellt sich heraus seine unsigned int
bitek
4
Ich würde eher sagen implizites int war in früheren Zeiten üblich. Es ist sicherlich nicht mehr länger und in C99 wurde es von C. entfernt
Jens
2
Es kann erwähnenswert sein, dass diese Antwort für Funktionsprototypen mit leeren Parameterlisten gilt, nicht jedoch für Funktionsdefinitionen mit leeren Parameterlisten.
autistisch
@mnemonicflow, bei dem es sich nicht um einen "nicht angegebenen Typ mit dem Standardwert int" handelt; unsignedist nur ein anderer Name für den gleichen Typ wie unsigned int.
Hobbs
58

int func();ist eine veraltete Funktionsdeklaration aus den Tagen, als es keinen C-Standard gab, dh aus den Tagen von K & R C (vor 1989, dem Jahr, in dem der erste "ANSI C" -Standard veröffentlicht wurde).

Denken Sie daran, dass es in K & R C keine Prototypen gab und das Schlüsselwort voidnoch nicht erfunden wurde. Sie können dem Compiler lediglich den Rückgabetyp einer Funktion mitteilen . Die leere Parameterliste in K & R C bedeutet "eine nicht spezifizierte, aber feste" Anzahl von Argumenten. Behoben bedeutet, dass Sie die Funktion jedes Mal mit der gleichen Anzahl von Argumenten aufrufen müssen (im Gegensatz zu einer variadischen Funktion wie printf, bei der Anzahl und Typ für jeden Aufruf variieren können).

Viele Compiler werden dieses Konstrukt diagnostizieren. Insbesondere gcc -Wstrict-prototypeserfahren Sie, dass "Funktionsdeklaration kein Prototyp ist", was genau richtig ist, da sie wie ein Prototyp aussieht (insbesondere, wenn Sie durch C ++ vergiftet sind!), dies aber nicht ist. Es ist eine alte K & R C-Rückgabetypdeklaration.

Faustregel: Lassen Sie niemals eine leere Parameterlistendeklaration leer, um int func(void)genau zu sein. Dadurch wird die Deklaration des K & R-Rückgabetyps zu einem geeigneten C89-Prototyp. Compiler sind glücklich, Entwickler sind glücklich, statische Prüfer sind glücklich. Diejenigen, die durch ^ W ^ Wfond von C ++ irregeführt werden, können jedoch zusammenzucken, weil sie zusätzliche Zeichen eingeben müssen, wenn sie versuchen, ihre Fremdsprachenkenntnisse zu üben :-)

Jens
quelle
1
Bitte beziehen Sie dies nicht auf C ++. Es ist normal, dass eine leere Argumentliste keine Parameter bedeutet , und die Menschen auf der Welt sind nicht daran interessiert, mit Fehlern umzugehen, die K & R-Leute vor etwa 40 Jahren gemacht haben. Aber Komitee-Experten ziehen immer wieder kontranatürliche Entscheidungen mit sich - in C99, C11.
Pfalcon
1
@pfalcon Der gesunde Menschenverstand ist ziemlich subjektiv. Was für eine Person gesunder Menschenverstand ist, ist für andere Wahnsinn. Professionelle C-Programmierer wissen, dass leere Parameterlisten eine nicht spezifizierte, aber feste Anzahl von Argumenten anzeigen. Eine Änderung würde wahrscheinlich viele Implementierungen beschädigen. Die Ausschüsse für die Vermeidung stiller Veränderungen zu beschuldigen, bellt meiner Meinung nach den falschen Baum an.
Jens
53
  • Die leere Parameterliste bedeutet "beliebige Argumente", daher ist die Definition nicht falsch.
  • Der fehlende Typ wird angenommen int.

Ich würde davon ausgehen, dass jedem Build, der dies übergibt, die konfigurierte Warn- / Fehlerstufe fehlt. Es macht keinen Sinn, dies zuzulassen, um tatsächlichen Code zuzulassen.

entspannen
quelle
30

Es ist K & R. Deklaration und Definition von Funktionen Stil. Ab C99-Norm (ISO / IEC 9899: TC3)

Abschnitt 6.7.5.3 Funktionsdeklaratoren (einschließlich Prototypen)

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. (Wenn beide Funktionstypen "alter Stil" sind, werden Parametertypen nicht verglichen.)

Abschnitt 6.11.6 Funktionsdeklaratoren

Die Verwendung von Funktionsdeklaratoren mit leeren Klammern (keine Parameterdeklaratoren im Prototypformat) ist eine veraltete Funktion.

Abschnitt 6.11.7 Funktionsdefinitionen

Die Verwendung von Funktionsdefinitionen mit separaten Parameterkennungs- und Deklarationslisten (keine Parametertyp- und Bezeichnerdeklaratoren im Prototypformat) ist ein veraltetes Merkmal.

Was der alte Stil K & R- Stil bedeutet

Beispiel:

Erklärung: int old_style();

Definition:

int old_style(a, b)
    int a; 
    int b;
{
     /* something to do */
}
Lei Mou
quelle
16

C geht davon aus, intdass für den Funktionsrückgabetyp und die Parameterliste kein Typ angegeben ist . Nur für diese Regel sind folgende seltsame Dinge möglich.

Eine Funktionsdefinition sieht so aus.

int func(int param) { /* body */}

Wenn es ein Prototyp ist, schreiben Sie

int func(int param);

Im Prototyp können Sie nur den Parametertyp angeben. Der Name der Parameter ist nicht obligatorisch. Damit

int func(int);

Auch wenn Sie keinen Parametertyp angeben, aber der Name intals Typ angenommen wird.

int func(param);

Wenn Sie weiter gehen, funktioniert das Folgende auch.

func();

Der Compiler geht davon aus, int func()wann Sie schreiben func(). Aber nicht func()in einen Funktionskörper stecken. Das wird ein Funktionsaufruf sein

Shiplu Mokaddim
quelle
3
Eine leere Parameterliste bezieht sich nicht auf den impliziten int-Typ. int func()ist keine implizite Form von int func(int).
John Bartholomew
11

Wie bereits bei @Krishnabhadra erwähnt, haben alle vorherigen Antworten anderer Benutzer eine korrekte Interpretation, und ich möchte nur einige Punkte genauer analysieren.

Nehmen Sie im Old-C wie im ANSI-C als " untypisierter formaler Parameter " die Dimension Ihres Arbeitsregisters oder Ihrer Befehlstiefenfähigkeit (Schattenregister oder Befehlskumulationszyklus) in einer 8-Bit-MPU als int16 in einem 16-Bit- Parameter MPU und so wird ein int16 sein und so weiter, falls 64-Bit-Architekturen Optionen wie -m32 kompilieren können.

Obwohl es auf hoher Ebene einfacher zu implementieren erscheint, wird die Arbeit des Programmierers im Schritt des Datendyps der Steuerdimensionierung anspruchsvoller, wenn mehrere Parameter übergeben werden.

In anderen Fällen nutzten die angepassten ANSI-Compiler für einige Mikroprozessorarchitekturen einige dieser alten Funktionen, um die Verwendung des Codes zu optimieren, und zwangen die Position dieser "untypisierten formalen Parameter", innerhalb oder außerhalb des Arbeitsregisters zu arbeiten fast das gleiche mit der Verwendung von "flüchtig" und "Register".

Es ist jedoch zu beachten, dass die modernsten Compiler keinen Unterschied zwischen den beiden Arten der Parameterdeklaration machen.

Beispiele für eine Kompilierung mit gcc unter Linux:

Haupt c

main2.c

main3.c  
In jedem Fall ist die lokale Angabe des Prototyps nutzlos, da kein Aufruf ohne Parameter erfolgt. Verweise auf diesen Prototyp sind nicht sinnvoll. Wenn Sie das System mit "untypisiertem Formalparameter" für einen externen Aufruf verwenden, generieren Sie einen deklarativen Prototyp-Datentyp.

So was:

int myfunc(int param);
RTOSkit
quelle
5
Ich habe mir die Mühe gemacht, Bilder so zu optimieren, dass die Größe der Bilder in Bytes so gering wie möglich war. Ich denke, die Bilder können einen schnellen Überblick über den Mangel an Unterschieden im generierten Code bekommen. Ich habe den Code getestet, eine echte Dokumentation dessen erstellt, was er behauptet, und nicht nur über das Problem theoretisiert. aber nicht alle sehen die Welt gleich. Es tut mir schrecklich leid, dass mein Versuch, der Community mehr Informationen zur Verfügung zu stellen, Sie so sehr gestört hat.
RTOSkit
1
Ich bin mir ziemlich sicher, dass es immer einen nicht spezifizierten Rückgabetyp gibt int, der im Allgemeinen entweder die Größe des Arbeitsregisters oder 16 Bit ist, je nachdem, welcher Wert kleiner ist.
Supercat
@supercat +1 Perfekter Abzug, du hast recht! Dies ist genau das, was ich oben besprochen habe. Ein Compiler, der standardmäßig für eine bestimmte Architektur entwickelt wurde, spiegelt immer die Größe des Arbeitsregisters der betreffenden CPU / MPU wider. In einer eingebetteten Umgebung gibt es also verschiedene Strategien zur erneuten Eingabe von a Echtzeit-Betriebssystem, in eine Compiler-Schicht (stdint.h) oder in eine Portabilitätsschicht, die das gleiche Betriebssystem in vielen anderen Architekturen portierbar macht und durch Ausrichten der CPU / MPU-spezifischen Typen (int, long, long long usw.) erhalten wird. mit den generischen Systemtypen u8, u16, u32.
RTOSkit
@supercat Ohne Steuerelementtypen, die von einem Betriebssystem oder einem bestimmten Compiler angeboten werden, müssen Sie die Entwicklungszeit etwas abwägen und sicherstellen, dass alle "nicht typisierten Zuweisungen" mit Ihrem Anwendungsdesign übereinstimmen, bevor Sie die Überraschung haben, eine zu finden. " 16bit int "und kein" 32bit int ".
RTOSkit
@ RTOSkit: Ihre Antwort legt nahe, dass der Standardtyp auf einem 8-Bit-Prozessor ein Int8 ist. Ich erinnere mich an ein paar C-ish-Compiler für die PICmicro-Markenarchitektur, bei denen dies der Fall war, aber ich glaube nicht, dass irgendetwas, das einem C-Standard im entferntesten ähnelt, jemals einen intTyp zugelassen hat, der nicht alle Werte im Bereich halten konnte - 32767 bis +32767 (Hinweis -32768 ist nicht erforderlich) und kann auch alle charWerte enthalten (dh wenn chares sich um 16 Bit handelt, muss es entweder signiert oder intgrößer sein).
Supercat
5

In Bezug auf den Parametertyp gibt es hier bereits richtige Antworten. Wenn Sie dies jedoch vom Compiler hören möchten, können Sie versuchen, einige Flags hinzuzufügen (Flags sind sowieso fast immer eine gute Idee).

Kompilieren Ihres Programms mit gcc foo.c -WextraIch bekomme:

foo.c: In function func’:
foo.c:5:5: warning: type of param defaults to int [-Wmissing-parameter-type]

Seltsamerweise -Wextrafängt dies nicht für clang(es erkennt -Wmissing-parameter-typeaus irgendeinem Grund nicht, vielleicht für historische, die oben erwähnt wurden), aber -pedantictut es:

foo.c:5:10: warning: parameter 'param' was not declared, 
defaulting to type 'int' [-pedantic]
int func(param)
         ^
1 warning generated.

Und für das Prototypproblem, wie oben noch einmal erwähnt, int func()bezieht es sich auf beliebige Parameter, es sei denn, Sie definieren es ausschließlich so int func(void), dass Sie dann die erwarteten Fehler erhalten:

foo.c: In function func’:
foo.c:6:1: error: number of arguments doesnt match prototype
foo.c:3:5: error: prototype declaration
foo.c: In function main’:
foo.c:12:5: error: too many arguments to function func
foo.c:5:5: note: declared here

oder in clangals:

foo.c:5:5: error: conflicting types for 'func'
int func(param)
    ^
foo.c:3:5: note: previous declaration is here
int func(void);
    ^
foo.c:12:20: error: too many arguments to function call, expected 0, have 1
    int bla = func(10);
              ~~~~ ^~
foo.c:3:1: note: 'func' declared here
int func(void);
^
2 errors generated.
keiner
quelle
3

Wenn die Funktionsdeklaration keine Parameter enthält, dh leer ist, werden nicht angegebene Argumente verwendet. Wenn Sie möchten, dass es keine Argumente enthält, ändern Sie es in:

int func(void);
PP
quelle
1
"Wenn der Funktionsprototyp keine Parameter hat" Dies ist kein Funktionsprototyp, sondern nur eine Funktionsdeklaration.
Effeffe
@effeffe hat das behoben. Ich wusste nicht, dass diese Frage viel Aufmerksamkeit bekommen würde;)
PP
3
Ist "Funktionsprototyp" nicht nur ein alternativer (möglicherweise altmodischer) Ausdruck für "Funktionsdeklaration"?
Giorgio
@Giorgio IMO, beide sind korrekt. "Funktionsprototyp" sollte mit "Funktionsdefinition" übereinstimmen. Ich denke wahrscheinlich, dass Effeffe eine Erklärung in der Frage bedeutete und was ich meinte, ist in meiner Antwort.
PP
@Giorgio nein, Funktionsdeklarationen werden durch den Rückgabetypwert, den Bezeichner und eine optionale Parameterliste erstellt. Funktionsprototypen sind Funktionsdeklarationen mit einer Parameterliste.
effeffe
0

Aus diesem Grund rate ich normalerweise Leuten, ihren Code zu kompilieren mit:

cc -Wmissing-variable-declarations -Wstrict-variable-declarations -Wold-style-definition

Diese Flags erzwingen ein paar Dinge:

  • -Wmissing-variable-Deklarationen: Es ist unmöglich, eine nicht statische Funktion zu deklarieren, ohne vorher einen Prototyp zu erhalten. Dies macht es wahrscheinlicher, dass ein Prototyp in einer Header-Datei mit der tatsächlichen Definition übereinstimmt. Alternativ wird erzwungen, dass Sie das statische Schlüsselwort zu Funktionen hinzufügen, die nicht öffentlich sichtbar sein müssen.
  • -Strenge Variablendeklarationen: Der Prototyp muss die Argumente ordnungsgemäß auflisten.
  • -Wold-Style-Definition: Die Funktionsdefinition selbst muss auch die Argumente korrekt auflisten.

Diese Flags werden standardmäßig auch in vielen Open Source-Projekten verwendet. In FreeBSD sind diese Flags beispielsweise aktiviert, wenn Sie mit WARNS = 6 in Ihrem Makefile erstellen.

Ed Schouten
quelle