2015-05-28 3 views
7

Ich weiß, dass Funktion Prototyping ist obligatorisch in C++, wenn die Funktion nach der main() Funktion definiert ist, aber es ist optional (aber empfohlen) in C. Ich schrieb vor kurzem ein einfaches Programm, das Hinzufügen von 2 Zahlen, aber versehentlich verwendet die Punktoperator anstelle eines Kommas beim Übergeben von Argumenten.Warum darf beim Aufruf einer Funktion in C nicht genügend Parameter übergeben werden?

#include <stdio.h> 
int main() 
{ 
    printf("sum is: %d",add(15.30)); // oops, uses dot instead of comma 
} 

int add(int a,int b) 
{ 
    return a+b; 
} 

In dem obigen Programm, wenn die add(int,int) Funktion vor der main() Funktion definiert wird, dann wird das Programm sicherlich nicht kompilieren. Dies liegt daran, dass beim Aufruf der Funktion weniger Parameter übergeben werden als erforderlich.

Aber, warum kompiliert das oben genannte Programm und läuft gut - geben einige große Müll Wert als eine Ausgabe? Was ist der Grund? Ist es besser, das Funktionsprototyping zu verwenden, damit der Compiler den Typenkonflikt und andere Fehler beim Funktionsaufruf erkennt?

Ist dieses undefinierte Verhalten?

+3

Es ist undefiniertes Verhalten, Warnungen verwenden, um diese Art von Fehler zu vermeiden. –

+0

IIRC, vor der Einführung der varargs-Syntax, würden Leute varargs fälschen, indem sie eine Reihe von Parametern deklarieren und dann einige nicht übergeben. Es war nie ein definiertes Verhalten, aber die Leute taten es, und später mussten die Compiler-Versionen den früheren Code unterstützen. – user2357112

+0

@ user2357112: Sind Sie sicher, dass das Verhalten in den Tagen, als Parameter zwischen der schließenden und der öffnenden Klammer aufgelistet wurden, nicht definiert wurde? Ich dachte, ich hätte in (1980s) Unix einige Funktionen gesehen, die den ersten Parameter benutzten, um zu entscheiden, ob man spätere Parameter untersuchen sollte, deren spätere Parameter manchmal weggelassen wurden, wenn sie nicht benutzt wurden. – supercat

Antwort

9

Wenn C sieht, dass Sie eine Funktion aufrufen, die noch nicht deklariert wurde, generiert es eine implizite Deklaration mit einer beliebigen Anzahl von Argumenten und nimmt eine Rückgabe von int an. In Ihrem Beispiel wird die implizite Deklaration von int add(); erstellt. Das ist natürlich falsch.

Zur Laufzeit wird die Add-Funktion ein einzelnes Doppel auf seinem Stapel erhalten und versuchen, die Bytes in einer verwirrenden und undefinierten Weise zu interpretieren.

C99 hat diese Regel geändert und verbietet das Aufrufen nicht deklarierter Funktionen, aber die meisten Compiler lassen es immer noch tun, wenn auch mit einer Warnung.

Die printf ist irrelevant.

+0

Um auf diese Antwort zu erweitern: Wenn Sie entweder einen Prototyp für Ihre Funktion deklarieren oder einfach vor dem Aufruf in der Datei definieren, erhalten Sie eine entsprechende Fehlermeldung. – larsks

+0

@larsks: es ist bereits in meiner Frage erwähnt .... – Destructor

+0

"die Add-Funktion wird ein einzelnes Doppel auf seinem Stapel erhalten und versuchen, die Bytes in einer verwirrenden und undefinierten Weise zu interpretieren." Hmm, aber was ist, wenn int int (int a, int b) {wenn (a == 15) 0 zurückgibt; return a + b;} ', wäre das in Ordnung? – chux

0

Warum darf beim Aufruf einer Funktion in C nicht genügend Parameter übergeben werden?

Es ist nicht.

Die Tatsache, dass Ihr Compiler nicht warnt/fehlschlägt, bedeutet nicht, dass es korrekt ist.

+2

Dies ist in C90 vollständig gültig und verursacht und implizite Deklaration (was leider mit der späteren Definition nicht kompatibel ist) Ein guter Compiler sollte jedoch warnen. – johannes

+0

@johannes: Einige Compiler würden identischen Code generieren, wenn sie zwei int-Werte an Methoden übergeben, die als int int() oder int int (int, int) deklariert sind. Solche Compiler können die spätere Definition als mit der früheren kompatibel ansehen. – supercat

+0

@johannes ** Sie liegen falsch. ** Der Aufruf einer Funktion mit der falschen Anzahl von Parametern ist ** undefiniertes Verhalten ** sogar in C90. ** Lies die Frage. ** Es geht nicht darum, die Parameterliste nicht anzugeben (was in C90 leider gültig war), sondern darum, die Funktion mit nicht genügend Argumenten aufzurufen. Sehen Sie sich einfach das Zitat an: "** pass ** ungenügende Anzahl von Parametern" - es sagt "pass" und nicht "deklariere". –

10

In den ersten Tagen der C, wenn Methoden wie sah:

int add(a,b) 
    int a; 
    int b; 
{ 
    return a+b; 
} 

gegeben int x; eine Aussage add(3,5); würde erzeugen Code wie:

push 5 
push 3 
call _add 
store r0,_x 

Der Compiler nicht brauchen würde nichts wissen über die add Methode über den Rückgabetyp (die es als int erraten könnte), um den obigen Code zu generieren; Wenn die Methode sich herausstellt, wird sie zur Zeit der Verbindung entdeckt. Code, der mehr Parameter als eine erwartete Routine lieferte, würde diese Werte auf den Stapel schieben, wo sie ignoriert würden, bis der Code sie einige Zeit später platzte.

Probleme würden auftreten, wenn der Code zu wenige Parameter, Parameter der falschen Typen oder beides übergibt. Wenn der Code weniger Parameter übergab als eine Routine erwartet hatte, hatte dies oft keine Auswirkung, wenn der aufgerufene Code nicht die späteren Parameter verwendete.Wenn Code gelesen Parameter, die nicht übergeben wurden, würden sie in der Regel "Müll" Werte lesen (obwohl es keine Garantie gibt, dass der Versuch, diese Variablen lesen würde keine anderen Nebenwirkungen haben). Auf vielen Plattformen wäre der erste nicht übergebene Parameter die Rückgabeadresse, wenn auch nicht immer. Wenn der Code in Parameter geschrieben wurde, die nicht übergeben wurden, würde das den Return-Stack in der Regel durcheinander bringen und zu merkwürdigem Verhalten führen.

Die Übergabe von inkorrekten Parametern würde häufig dazu führen, dass die aufgerufene Funktion an ihren falschen Stellen nach dem falschen Ort sucht. Wenn die übergebenen Typen kleiner als erwartet waren, konnte dies dazu führen, dass die aufgerufene Methode auf Stack-Variablen zugreift, die ihr nicht gehören (wie im Fall der Übergabe von zu wenigen Parametern).

ANSI C standardisierte die Verwendung von Funktionsprototypen; Wenn der Compiler eine Definition oder Deklaration wie int add(int a, int b) sieht, bevor er einen Aufruf an add sieht, dann stellt er sicher, dass er zwei Argumente vom Typ int übergibt, auch wenn der Aufrufer etwas anderes liefert (zB wenn der Aufrufer eine double übergibt, wird sein Wert gezwungen werden, vor dem Anruf int einzugeben). Ein Versuch, zu wenig Parameter an eine Methode zu übergeben, die der Compiler gesehen hat, wird zu einem Fehler führen.

Im obigen Code wird der Aufruf an add vorgenommen, bevor der Compiler irgendeine Ahnung hat, was die Methode erwartet. Während neuere Dialekte eine solche Verwendung nicht zulassen würden, würde der Compiler standardmäßig davon ausgehen, dass add eine Methode ist, die Argumente übernimmt, die der Aufrufer liefert, und int zurückgibt. In dem obigen Fall würde der aufgerufene Code wahrscheinlich erwarten, dass zwei Wörter auf den Stapel geschoben werden, aber die Konstante double würde wahrscheinlich als zwei oder vier Wörter gedrückt werden. Somit würde das Verfahren wahrscheinlich für a und b zwei Wörter aus der binären Darstellung des double Wertes 15.3 erhalten.

BTW, selbst Compiler, die die angegebene Syntax akzeptieren, quieken fast immer, wenn eine Funktion, deren Typ implizit angenommen wurde, als etwas zurückgegeben wird, das nicht int ist; Viele werden auch quieken, wenn eine solche Funktion mit etwas anderem als der alten Syntax definiert wird. Während die ersten C-Compiler immer Funktionsargumente auf den Stack geschoben haben, erwarten neuere sie normalerweise in Registern. wenn man also

/* Old-style declaration for function that accepts whatever it's given 
    and returns double */ 

double add(); 

dann der Code erklären add(12.3,4.56) zwei double Werte auf den Stapel schieben würde und rufen Sie die add Funktion, aber wenn man stattdessen erklärt hatte:

double add(double a, double b); 

dann auf vielen Systemen Der Code add(12.3,4.56) würde vor dem Aufruf das Gleitkommaregister 0 mit 12.3 und das Gleitkommaregister 1 mit 4.56 laden (nichts schieben). Folglich sind die beiden Deklarationstypen nicht kompatibel. Wenn Sie auf solchen Systemen versuchen, eine Methode aufzurufen, ohne sie zu deklarieren, und sie anschließend in der gleichen Kompilierungseinheit unter Verwendung der Syntax im neuen Stil deklarieren, wird ein Kompilierungsfehler verursacht. Ferner geben einige dieser Systeme den Namen von Funktionen, die Registerargumente mit zwei und nicht mit einem Unterstrich belegen, die Präfixe an, wenn die Methode mit einer neuen Stilsyntax definiert wurde, aber aufgerufen wurde, ohne dass der Compiler einen Prototyp gesehen hat würde versuchen, aufzurufen, obwohl die Funktion __add genannt wurde. Dies würde den Linker dazu veranlassen, das Programm abzulehnen, anstatt eine ausführbare Datei zu erzeugen, die nicht funktionieren würde.

Verwandte Themen