Ich verwende den folgenden Code:
char dest[5];
char src[5] = "test";
printf("String: %s\n", do_something(dest, src));
char *do_something(char *dest, const char *src)
{
return dest;
}
Die Implementierung von do_something
ist hier nicht wichtig. Wenn ich versuche, das Obige zu kompilieren, erhalte ich diese beiden Ausnahmen:
Fehler: widersprüchliche Typen für 'do_something' (beim Aufruf von printf)
Fehler: Die vorherige implizite Deklaration von 'do_something' war hier (in der Prototypzeile).
Warum?
Antworten:
Sie versuchen, do_something aufzurufen, bevor Sie es deklarieren. Sie müssen vor Ihrer printf-Zeile einen Funktionsprototyp hinzufügen:
char* do_something(char*, const char*);
Oder Sie müssen die Funktionsdefinition über die printf-Zeile verschieben. Sie können eine Funktion nicht verwenden, bevor sie deklariert wurde.
quelle
In der "klassischen" C-Sprache (C89 / 90) geht C beim Aufrufen einer nicht deklarierten Funktion davon aus, dass sie eine zurückgibt,
int
und versucht auch, die Typen ihrer Parameter aus den Typen der tatsächlichen Argumente abzuleiten (nein, das wird nicht angenommen Es hat keine Parameter, wie jemand zuvor vorgeschlagen hat.In Ihrem speziellen Beispiel würde der Compiler den
do_something(dest, src)
Aufruf betrachten und implizit eine Deklaration für ableitendo_something
. Letzteres würde wie folgt aussehenint do_something(char *, char *)
Später im Code deklarieren Sie jedoch ausdrücklich
do_something
alschar *do_something(char *, const char *)
Wie Sie sehen können, unterscheiden sich diese Erklärungen voneinander. Das mag der Compiler nicht.
quelle
int foo()
um die Funktionfoo
offiziell deklariert zu machen . In C (im Gegensatz zu C ++)()
bedeutet Funktionsdeklaration immer noch, dass die Funktionsparameterliste nicht spezifiziert ist und vom Compiler genau wie oben beschrieben "abgeleitet" werden muss. Dh C-Compiler leiten keine Funktionsrückgabetypen mehr ab, aber Parameterlisten können weiterhin abgeleitet werden.int (char *, char *)
während der tatsächliche Funktionstyp istchar *(char *, const char *)
. Die allgemeine Regel, gegen die in diesem Fall verstoßen wird, gilt jedoch für alle Arten von Deklarationen (nicht nur "abgeleitete", nicht nur Funktionsdeklarationen). Sie können beispielsweise deklarierenextern double a;
und dann definieren,int a;
und Sie erhalten denselben Fehler bei widersprüchlichen Typen. Und anscheinend glaubten die GCC-Autoren nicht, dass Funktionen eine getrennte Behandlung verdienen.Hintergrund der AC-Funktionsdeklaration
In C funktionieren Funktionsdeklarationen nicht wie in anderen Sprachen: Der C-Compiler selbst sucht in der Datei nicht vorwärts und rückwärts, um die Funktionsdeklaration an der Stelle zu finden, an der Sie sie aufrufen, und scannt die Datei nicht mehr Male , um herauszufinden , die Beziehungen entweder: der Compiler nur untersucht , nach vorn in der Datei genau einmal , von oben nach unten. Das Verbinden von Funktionsaufrufen mit Funktionsdeklarationen ist Teil der Aufgabe des Linkers und erfolgt erst, nachdem die Datei bis zu den Anweisungen für die unformatierte Assembly kompiliert wurde.
Dies bedeutet, dass beim Durchlaufen der Datei durch den Compiler, wenn der Compiler zum ersten Mal auf den Namen einer Funktion stößt, eines von zwei Dingen der Fall sein muss: Entweder wird die Funktionsdeklaration selbst angezeigt. In diesem Fall weiß der Compiler Bescheid genau, was die Funktion ist und welche Typen sie als Argumente verwendet und welche Typen sie zurückgibt - oder es ist ein Aufruf der Funktion, und der Compiler muss raten, wie die Funktion schließlich deklariert wird.
(Es gibt eine dritte Option, bei der der Name in einem Funktionsprototyp verwendet wird, aber wir werden dies vorerst ignorieren, da Sie wahrscheinlich keine Prototypen verwenden, wenn Sie dieses Problem überhaupt sehen.)
Geschichtsunterricht
In den frühesten Tagen von C war die Tatsache, dass der Compiler Typen erraten musste, kein wirkliches Problem: Alle Typen waren mehr oder weniger gleich - so ziemlich alles war entweder ein Int oder ein Zeiger, und das waren sie auch die gleiche Größe. (Tatsächlich gab es in B, der Sprache vor C, überhaupt keine Typen; alles war nur ein Int oder ein Zeiger, und sein Typ wurde ausschließlich davon bestimmt, wie Sie ihn verwendet haben!) Der Compiler konnte also das Verhalten von jedem sicher erraten Funktion nur basierend auf der Anzahl der übergebenen Parameter: Wenn Sie zwei Parameter übergeben würden, würde der Compiler zwei Dinge auf den Aufrufstapel schieben, und vermutlich würde der Angerufene zwei Argumente deklarieren, und das würde alles in einer Reihe stehen. Wenn Sie nur einen Parameter übergeben würden, die Funktion jedoch zwei erwarten würde, würde dies immer noch funktionieren, und das zweite Argument würde einfach ignoriert / garbage. Wenn Sie drei Parameter übergeben und die Funktion zwei erwartet, würde dies ebenfalls funktionieren, und der dritte Parameter würde von den lokalen Variablen der Funktion ignoriert und darauf getreten. (Einige alte C-Codes erwarten immer noch, dass diese Regeln für nicht übereinstimmende Argumente auch funktionieren.)
Mit dem Compiler können Sie jedoch alles an etwas übergeben, um eine Programmiersprache zu entwerfen. In den frühen Tagen funktionierte es gut, da die frühen C-Programmierer meistens Assistenten waren und wussten, dass sie den falschen Typ nicht an Funktionen weitergeben sollten, und selbst wenn sie die Typen falsch verstanden hatten, gab es immer
lint
solche Tools , die eine eingehendere Überprüfung durchführen konnten von Ihrem C-Code und warnen Sie vor solchen Dingen.Schneller Vorlauf bis heute, und wir sitzen nicht ganz im selben Boot. C ist erwachsen geworden, und viele Leute programmieren darin, die keine Zauberer sind, und um sie unterzubringen (und um alle anderen unterzubringen, die es
lint
sowieso regelmäßig benutzen ), haben die Compiler viele der Fähigkeiten übernommen, zu denen sie zuvor gehörtenlint
- insbesondere der Teil, in dem Ihr Code überprüft wird, um sicherzustellen, dass er typsicher ist. Frühe C-Compiler ließen Sie schreibenint foo = "hello";
und wiesen den Zeiger nur munter auf die Ganzzahl zu, und es lag an Ihnen, sicherzustellen, dass Sie nichts Dummes taten. Moderne C-Compiler beschweren sich lautstark, wenn Sie Ihre Typen falsch verstehen, und das ist gut so.Typ Konflikte
Was hat das alles mit dem mysteriösen Konfliktfehler in der Zeile der Funktionsdeklaration zu tun? Wie ich oben sagte, müssen C-Compiler immer noch wissen oder raten, was ein Name bedeutet, wenn sie diesen Namen zum ersten Mal sehen, wenn sie die Datei vorwärts scannen: Sie können wissen, was es bedeutet, wenn es sich um eine tatsächliche Funktionsdeklaration selbst handelt (oder um eine Funktion "Prototyp", mehr dazu in Kürze), aber wenn es nur ein Aufruf der Funktion ist, müssen sie raten . Und leider ist die Vermutung oft falsch.
Als der Compiler Ihren Aufruf von sah
do_something()
, sah er sich an, wie er aufgerufen wurde, und kam zu dem Schluss, dassdo_something()
dies schließlich wie folgt deklariert werden würde:int do_something(char arg1[], char arg2[]) { ... }
Warum kam es zu dem Schluss? Weil du es so genannt hast ! (Einige C - Compiler schließen kann , dass es
int do_something(int arg1, int arg2)
oder einfachint do_something(...)
, beide sind noch weiter von dem, was Sie wollen, aber der wichtige Punkt ist , dass unabhängig davon , wie der Compiler die Typen Vermutungen, es errät sie anders aus , was Ihre eigentliche Funktion verwendet. )Später, wenn der Compiler in der Datei vorwärts scannt, sieht er Ihre tatsächliche Deklaration von
char *do_something(char *, char *)
. Diese Funktionsdeklaration kommt nicht einmal der vom Compiler erratenen Deklaration nahe, was bedeutet, dass die Zeile, in der der Compiler den Aufruf kompiliert hat, falsch kompiliert wurde und das Programm einfach nicht funktioniert. Es wird also zu Recht ein Fehler ausgegeben, der Sie darüber informiert, dass Ihr Code nicht wie geschrieben funktioniert.Sie fragen sich vielleicht: "Warum wird davon ausgegangen, dass ich eine zurückgebe
int
?" Nun, es wird dieser Typ angenommen, weil es keine gegenteiligen Informationen gibt:printf()
Kann jeden Typ in seinen variablen Argumenten aufnehmen, ist also ohne eine bessere Antwortint
eine ebenso gute Vermutung wie jede andere. (Viele frühe C-Compiler haben immerint
für jeden nicht spezifizierten Typ angenommen, und Sie haben...
für die Argumente für jede deklarierte Funktion gemeintf()
- nichtvoid
-, weshalb viele moderne Codestandards empfehlen, immervoid
die Argumente einzugeben, wenn es wirklich keine geben soll .)Die Reparatur
Es gibt zwei allgemeine Korrekturen für den Funktionsdeklarationsfehler.
Die erste Lösung, die von vielen anderen Antworten hier empfohlen wird, besteht darin, einen Prototyp in den Quellcode über der Stelle zu setzen, an der die Funktion zum ersten Mal aufgerufen wird. Ein Prototyp sieht genauso aus wie die Deklaration der Funktion, hat jedoch ein Semikolon, in dem sich der Körper befinden sollte:
char *do_something(char *dest, const char *src);
Wenn der Prototyp an erster Stelle steht, weiß der Compiler dann , wie die Funktion letztendlich aussehen wird, sodass er nicht raten muss. Konventionell setzen Programmierer Prototypen häufig ganz oben in die Datei, direkt unter den
#include
Anweisungen, um sicherzustellen, dass sie immer definiert werden, bevor sie möglicherweise verwendet werden.Die andere Lösung, die auch in einem realen Code angezeigt wird, besteht darin, Ihre Funktionen einfach neu zu ordnen, sodass die Funktionsdeklarationen immer vor allem stehen, was sie aufruft! Sie könnten die gesamte
char *do_something(char *dest, const char *src) { ... }
Funktion über den ersten Aufruf verschieben, und der Compiler würde dann genau wissen, wie die Funktion aussieht, und müsste nicht raten.In der Praxis verwenden die meisten Benutzer Funktionsprototypen, da Sie auch Funktionsprototypen in Header (
.h
) -Dateien verschieben können, damit Code in anderen.c
Dateien diese Funktionen aufrufen kann. Beide Lösungen funktionieren jedoch, und viele Codebasen verwenden beide.C99 und C11
Es ist nützlich zu beachten, dass sich die Regeln in den neueren Versionen des C-Standards geringfügig unterscheiden. In den früheren Versionen (C89 und K & R), der Compiler wirklich würden die Typen bei Funktionsaufruf Zeit erraten (und K & R-Ära Compiler oft würden Sie nicht einmal warnen , wenn sie falsch sind). C99 und C11 erfordern beide, dass die Funktionsdeklaration / der Prototyp vor dem ersten Aufruf steht, und es ist ein Fehler, wenn dies nicht der Fall ist. Viele moderne C-Compiler - hauptsächlich aus Gründen der Abwärtskompatibilität mit früherem Code - warnen jedoch nur vor einem fehlenden Prototyp und betrachten ihn nicht als Fehler.
quelle
Sie haben es nicht deklariert, bevor Sie es verwendet haben.
Du brauchst so etwas wie
char *do_something(char *, const char *);
vor dem printf.
Beispiel:
#include <stdio.h> char *do_something(char *, const char *); char dest[5]; char src[5] = "test"; int main () { printf("String: %s\n", do_something(dest, src)); return 0; } char *do_something(char *dest, const char *src) { return dest; }
Alternativ können Sie die gesamte
do_something
Funktion vor den printf stellen.quelle
Sie müssen die Funktion deklarieren, bevor Sie sie verwenden. Wenn der Funktionsname vor seiner Deklaration erscheint, folgt der C-Compiler bestimmten Regeln und erstellt die Deklaration selbst. Wenn es falsch ist, erhalten Sie diesen Fehler.
Sie haben zwei Möglichkeiten: (1) Definieren Sie es, bevor Sie es verwenden, oder (2) Verwenden Sie die Vorwärtsdeklaration ohne Implementierung. Zum Beispiel:
char *do_something(char *dest, const char *src);
Beachten Sie das Semikolon am Ende.
quelle
C Gebot Nr. 3:
K&R #3 Thou shalt always prototype your functions or else the C compiler will extract vengence.
http://www.ee.ryerson.ca:8080/~elf/hack/God.vs.K+R.html
quelle
Wenn Sie vor der Verwendung keinen Prototyp für die Funktion angeben, geht C davon aus, dass eine beliebige Anzahl von Parametern verwendet wird, und gibt ein int zurück. Wenn Sie also zum ersten Mal versuchen, do_something zu verwenden, ist dies die Art von Funktion, nach der der Compiler sucht. Dies sollte eine Warnung vor einer "impliziten Funktionsdeklaration" erzeugen.
Wenn Sie also in Ihrem Fall die Funktion später tatsächlich deklarieren, lässt C keine Funktionsüberladung zu, sodass es pissig wird, weil Sie zwei Funktionen mit unterschiedlichen Prototypen, aber demselben Namen deklariert haben.
Kurze Antwort: Deklarieren Sie die Funktion, bevor Sie versuchen, sie zu verwenden.
quelle
int foo()
(nimmt eine beliebige Anzahl von Parametern an) undint foo(void)
(nimmt keine Parameter an).Nochmals ansehen:
char dest[5]; char src[5] = "test"; printf("String: %s\n", do_something(dest, src));
Konzentrieren Sie sich auf diese Linie:
printf("String: %s\n", do_something(dest, src));
Sie können deutlich sehen, dass die Funktion do_something nicht deklariert ist!
Wenn Sie etwas weiter suchen,
printf("String: %s\n", do_something(dest, src)); char *do_something(char *dest, const char *src) { return dest; }
Sie werden sehen, dass Sie die Funktion deklarieren, nachdem Sie sie verwendet haben.
Sie müssen diesen Teil mit folgendem Code ändern:
char *do_something(char *dest, const char *src) { return dest; } printf("String: %s\n", do_something(dest, src));
Prost ;)
quelle
Dies passiert häufig, wenn Sie die Definition der Wechselstromfunktion ändern und vergessen, die entsprechende Headerdefinition zu aktualisieren.
quelle
Stellen Sie sicher, dass die Typen in der Funktionsdeklaration zuerst deklariert werden.
/* start of the header file */
.
.
.
struct intr_frame{...}; //must be first!
.
.
.
void kill (struct intr_frame *);
.
.
.
/* end of the header file */
quelle