Warum erhalten Sie in C „widersprüchliche Funktionstypen“?

74

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

goe
quelle
1
Ich habe denselben Fehler erhalten, aber ich habe die Funktion noch nirgendwo aufgerufen (weder vor noch nach ihrer Definition), sodass die ausgewählte Antwort in meinem Fall nicht zutreffend war. Es stellte sich heraus, dass das Problem ein Namenskonflikt war - der Name meiner Funktion war "mergesort" (ich folge zusammen mit Skienas "Algorithm Design Manual") und es gibt bereits eine identisch benannte Funktion in stdlib.h. Das Umbenennen meiner Funktion in "merge_sort" hat den Trick getan.
Richie Thomas

Antworten:

130

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.

James McNellis
quelle
26

In der "klassischen" C-Sprache (C89 / 90) geht C beim Aufrufen einer nicht deklarierten Funktion davon aus, dass sie eine zurückgibt, intund 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 ableiten do_something. Letzteres würde wie folgt aussehen

int do_something(char *, char *)

Später im Code deklarieren Sie jedoch ausdrücklich do_somethingals

char *do_something(char *, const char *)

Wie Sie sehen können, unterscheiden sich diese Erklärungen voneinander. Das mag der Compiler nicht.

Ameise
quelle
Gibt es Fälle, in denen Sie eine nicht deklarierte Funktion legal aufrufen können? Was ist der Grund für einen Fehler "widersprüchliche Typen" für dieses Problem anstelle einer "Funktion, die zuvor nicht deklariert wurde" -Fehler?
Intcreator
2
@brandaemon: Die ursprüngliche C-Sprache (C89 / 90) hatte keinen Fehler "Funktion nicht deklariert, bevor sie aufgerufen wurde". Es war völlig legal, nicht deklarierte Funktionen in C aufzurufen. Anstatt einen Fehler auszugeben, musste die Sprache den Aufruf analysieren und die implizite Deklaration für die Funktion "ableiten" . So wurde die Sprache ursprünglich entworfen. Ja, in C89 / 90 können Sie eine nicht deklarierte Funktion legal aufrufen, solange die "abgeleitete" Funktionsdeklaration mit der tatsächlichen kompatibel ist .
AnT
2
@brandaemon: C99 hat das Aufrufen nicht deklarierter Funktionen verboten. Das heißt, ab C99 gibt es in C den Fehler "Funktion nicht deklariert, bevor sie aufgerufen wurde". Selbst in C99 (und höher) ist es jedoch möglich, eine Funktion ohne Prototyp zu deklarieren. Dh es reicht aus, int foo()um die Funktion foooffiziell 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.
Am
Ok, ich glaube ich verstehe. Wurde dieser spezielle Compilerfehler absichtlich für Typen verwendet oder könnte er für spezielle Fälle, in denen das Problem bei Funktionsdeklarationen liegt, umformuliert worden sein (und möglicherweise in einigen Compilern? Ich habe es gerade auf gcc so gesehen)? Ich versuche hauptsächlich zu sehen, wie man diesen bestimmten Fehler besser interpretiert, da er zumindest in diesem Fall nicht sehr intuitiv erscheint.
Intcreator
1
@brandaemon: Der Compilerfehler im ursprünglichen Beitrag bezieht sich tatsächlich auf Funktionstypen . Es heißt, dass der abgeleitete Funktionstyp ist, int (char *, char *)während der tatsächliche Funktionstyp ist char *(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 deklarieren extern 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.
Am
9

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 lintsolche 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 lintsowieso regelmäßig benutzen ), haben die Compiler viele der Fähigkeiten übernommen, zu denen sie zuvor gehörten lint- insbesondere der Teil, in dem Ihr Code überprüft wird, um sicherzustellen, dass er typsicher ist. Frühe C-Compiler ließen Sie schreiben int 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, dass do_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 einfach int 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 Antwort inteine ebenso gute Vermutung wie jede andere. (Viele frühe C-Compiler haben immer intfür jeden nicht spezifizierten Typ angenommen, und Sie haben ...für die Argumente für jede deklarierte Funktion gemeint f()- nicht void-, weshalb viele moderne Codestandards empfehlen, immer voiddie 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 #includeAnweisungen, 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 .cDateien 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.

Sean Werkema
quelle
Vielen Dank für Ihre ausführliche und nachdenkliche Antwort
Tycholiz
8

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_somethingFunktion vor den printf stellen.


quelle
6

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.

Viliam
quelle
3

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.

mrduclaw
quelle
3
Nein, es wird davon ausgegangen, dass eine beliebige Anzahl von Parametern verwendet wird und ein int zurückgegeben wird. In C gibt es einen großen Unterschied zwischen int foo()(nimmt eine beliebige Anzahl von Parametern an) und int foo(void)(nimmt keine Parameter an).
Adam Rosenfield
Danke für die Klarstellung! Aktualisiert :)
mrduclaw
3

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

EKons
quelle
1

Dies passiert häufig, wenn Sie die Definition der Wechselstromfunktion ändern und vergessen, die entsprechende Headerdefinition zu aktualisieren.

ChrisEisenhart
quelle
Ich hatte etwas Ähnliches passiert. Ich habe Bindungen für openCL gegen die Khronos cl.h-Headerdatei geschrieben. Ich habe aus der Online-Dokumentation kopiert und eingefügt, um Typedefs für Funktionsprototypen / Signaturen zu erstellen. Es stellt sich heraus, dass die Dokumentation für clGetContextInfo für openCL 1.0 falsch ist. Der letzte Parameter sollte "size_t *" sein, war aber "size_t".
JMI MADISON
0

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 */

Dávid Natingga
quelle