2010-09-21 7 views
24

ich wikipedia auf C/C++ Prototype Aussagen zu lesen und ich bin verwirrt:Zweck der C/C++ Prototypen

Wikipedia sagen: „Durch den Funktionsprototyp einschließlich, informieren Sie den Compiler, dass die Funktion‚fac‘nimmt ein Integer-Argument und Sie ermöglichen dem Compiler, diese Art von Fehlern zu erfassen. "

und verwendet die unten als Beispiel:

#include <stdio.h> 

/* 
    * If this prototype is provided, the compiler will catch the error 
    * in main(). If it is omitted, then the error will go unnoticed. 
    */ 
int fac(int n);    /* Prototype */ 

int main(void) {    /* Calling function */ 
    printf("%d\n", fac()); /* ERROR: fac is missing an argument! */ 
    return 0; 
} 

int fac(int n) {    /* Called function */ 
    if (n == 0) 
     return 1; 
    else 
     return n * fac(n - 1); 
} 

Aber der Funktionsdefinition der aufgerufenen Funktion enthält bereits alle die Information, dass der Prototyp der Compiler sagt, warum also nicht der Compiler ableiten diese Informationen aus der Definition der aufgerufenen Funktion, da sie identische Aussagen/Informationsschreiben für den Brief enthalten?

Was fehlt mir? Scheint wie zusätzliche Arbeit für keinen offensichtlichen Gewinn.

Bearbeiten: Danke Jungs. Ich nahm an, dass die Compiler Multi-Pass waren, denke ich. Ich bin verwöhnt mit aktuellen Sprachen wie Python. Es macht Sinn, da es so alt ist, dass man etwas Kludges braucht, um Dinge in einem einzigen Durchgang genau zu machen. Es scheint mir jetzt offensichtlicher. Offensichtlich erfordert es ziemlich genaues Wissen darüber, wie der Compiler verknüpft und kompiliert.

+7

Beachten Sie, dass dieser Wikipedia-Artikel falsche Dinge enthält. Sie können auf der Diskussionsseite sehen, wie einige Leute es einfach nicht korrigieren lassen. Ich habe das aufgegeben. –

+0

Deshalb definiere ich Funktionen, bevor sie aufgerufen werden (jedenfalls in der gleichen Quelldatei). Dadurch entfällt die separate Deklaration, obwohl mein Code "rückwärts" lautet. –

Antwort

16

Mit Prototypen können Sie die Schnittstelle von der Implementierung trennen.

In Ihrem Beispiel lebt der gesamte Code in einer Datei, und Sie könnten ebenso einfach die fac() - Definition dorthin verschoben haben, wo sich der Prototyp gerade befindet, und den Prototyp entfernen.

Real-World-Programme bestehen aus mehreren .cpp-Dateien (auch Kompilierungseinheiten genannt), die häufig kompiliert und in Bibliotheken verknüpft werden, bevor sie mit dem endgültigen ausführbaren Formular verknüpft werden. Bei großen Projekten dieser Art werden Prototypen in .h-Dateien (aka Header-Dateien) gesammelt, wobei der Header zum Kompilieren in anderen Kompilierungseinheiten enthalten ist, um den Compiler auf die Existenz- und Aufrufkonventionen der Funktionalität in der Bibliothek aufmerksam zu machen. In diesen Fällen steht die Funktionsdefinition dem Compiler nicht zur Verfügung. Daher dienen die Prototypen (auch Deklarationen genannt) als eine Art Vertrag, der die Fähigkeiten und Anforderungen der Bibliothek definiert.

3

Der C-Compiler verarbeitet Quelldateien von oben nach unten. Funktionen, die nach erscheinen, werden bei der Auflösung von Argumenttypen nicht berücksichtigt. Also, in Ihrem Beispiel, wenn main() am Ende der Datei war, dann würden Sie keinen Prototyp für fac() benötigen (da die Definition von fac() bereits vom Compiler beim Kompilieren main() gesehen worden wäre).

26

Zwei Gründe:

  1. Der Compiler die Datei von oben nach unten gelesen. Wenn fac in main verwendet wird, was über fac ist und kein Prototyp vorhanden ist, kann der Compiler nicht überprüfen, ob dieser Aufruf korrekt ausgeführt wird, da er noch nicht die Definition von fac erreicht hat.

  2. Es ist möglich, ein C- oder C++ - Programm in mehrere Dateien aufzuteilen. fac kann in einer völlig anderen Datei als die Datei definiert werden, die der Compiler gerade verarbeitet, und muss daher wissen, dass diese Funktion irgendwo existiert und wie sie aufgerufen werden soll.

Beachten Sie, dass die Kommentare in dem Beispiel nur bis C. In C++ gelten gepostet, dass Beispiel wird immer erzeugt einen Fehler, auch wenn der Prototyp weggelassen wird (obwohl es einen anderen Fehler erzeugen wird in Abhängigkeit von ob der Prototyp existiert oder nicht). In C++ müssen alle Funktionen vor der Verwendung definiert oder prototypisiert werden.

In C können Sie den Prototyp weglassen, und der Compiler wird Ihnen erlauben, die Funktion mit einer beliebigen Anzahl von Argumenten (einschließlich Null) aufzurufen und wird den Rückgabetyp int annehmen. Aber nur weil es Sie während des Kompilierens nicht anschreien bedeutet das nicht, dass das Programm richtig funktioniert, wenn Sie die Funktion nicht den richtigen Weg nennen. Deshalb ist es nützlich, in C zu prototypieren, damit der Compiler in Ihrem Namen überprüfen kann.

Die Philosophie hinter C und C++, die diese Art von Funktion motiviert, ist, dass es sich um relativ Low-Level-Sprachen handelt. Sie machen nicht viel Hand-halten und sie tun nicht viel, wenn irgendwelche Laufzeit-Überprüfung. Wenn Ihr Programm etwas falsch macht, wird es abstürzen oder sich bizarr verhalten. Daher enthalten die Sprachen Funktionen wie diese, die es dem Compiler ermöglichen, bestimmte Arten von Fehlern zur Kompilierungszeit zu identifizieren, sodass Sie sie leichter finden und beheben können.

+0

Wenn # 2 nicht enthält oder etwas von diesem Typ sagen Sie ihm, in welcher Datei die echte Funktion zu finden ist? In Python würden Sie nur das Modul importieren und dann die Funktion als module.function referenzieren, hat c keine Namespaces, die so referenziert werden können? – pythonnewbie

+0

Wie löst es # 1 auf? Nichts in dem Prototyp teilt dem Compiler mit, dass Facs die Haupt- und/oder die Hauptaufrufe facs nennt. – pythonnewbie

+0

Im Fall # 2, was denkst du, enthält die Includes? Sie enthalten Prototypen! Wenn Sie jedoch Ihre eigene 'fac'-Funktion schreiben, * müssen * Sie den Prototyp bereitstellen, unabhängig davon, ob es sich um eine separate Include-Datei oder um dieselbe Quelldatei handelt. Im Fall # 1 (ich nehme an, Sie beziehen sich hier auf die Antwort von Mystagogue), besteht das Problem nicht darin, dass mit einer rekursiven Abhängigkeit etwas nicht stimmt, wenn Sie eine rekursive Abhängigkeit haben, gibt es keine Möglichkeit, die beiden Funktionen zu ordnen so dass der Compiler die Definition von beiden vor der Verarbeitung eines Aufrufs liest. –

4

Der wichtigste Grund für den Prototyp ist die Auflösung zirkulärer Abhängigkeiten. Wenn "main" "fac" und "fac" "main" aufrufen kann, benötigen Sie einen Prototyp, um das Problem zu lösen.

+0

Dies, zusammen mit der Verknüpfung mit einer bereits kompilierten Bibliothek, sind wirklich die zwei Gründe, warum eine Deklaration notwendig ist.Alle anderen sind Kosmetik/Vorlieben. – codechimp

1

Neben all den guten Antworten, denken Sie darüber nach: Wenn Sie ein Compiler wären, und Ihre Aufgabe wäre Quellcode in Maschinensprache zu übersetzen, und Sie (der pflichtbewusste Compiler, der Sie sind) konnten nur eine Quelle lesen Code Zeile für Zeile - wie würden Sie den eingefügten Code lesen, wenn es keinen Prototyp gäbe? Woher weißt du, dass der Funktionsaufruf gültig ist und kein Syntaxfehler? (Ja, Sie könnten eine Notiz machen und am Ende prüfen, ob alles übereinstimmt, aber das ist eine andere Geschichte).

Ein anderer Weg, um es (diesmal als Mensch) aussehen: Angenommen, Sie nicht die Funktion hat, als Prototyp definiert, noch ist sein Quellcode zur Verfügung. Sie wissen jedoch, dass in der Bibliothek, die Ihr Kumpel Ihnen gab, der Maschinencode ist, der beim Ausführen ein bestimmtes erwartetes Verhalten zurückgibt. Wie schön. Nun, wie würden Sie Compiler wissen, dass ein solcher Funktionsaufruf gültig ist, wenn es keinen Prototyp gibt, der sagt: "Hey, ihr vertraut mir, es gibt eine Funktion, die so genannt wird und solche Parameter nimmt und etwas zurückgibt"?

Ich weiß, es ist eine sehr, sehr, sehr simplistic Art darüber nachzudenken. Das Hinzufügen von Intentionalität zu Softwareteilen ist wahrscheinlich ein schlechtes Zeichen, oder?

4

C und C++ sind zwei verschiedene Sprachen, und in diesem speziellen Fall gibt es einen großen Unterschied zwischen den beiden. Aus dem Inhalt der Frage gehe ich davon aus, dass Sie über C. sprechen

#include <stdio.h> 
int main() { 
    print(5, "hi"); // [1] 
} 
int print(int count, const char* txt) { 
    int i; 
    for (i = 0; i < count; ++i) 
     printf("%s\n", txt); 
} 

Das ist ein richtiges C-Programm, das tut, was Sie erwarten können: prints 5 Zeilen „hallo“ in jedem von ihnen sagen. Die C-Sprache findet den Aufruf bei [1] geht davon aus, dass print ist eine Funktion, die int zurückgibt und eine unbekannte Anzahl von Argumenten (unbekannt für den Compiler, dem Programmierer bekannt), nimmt der Compiler an, dass der Aufruf korrekt ist und setzt das Kompilieren fort. Da die Funktionsdefinition und der Aufruf übereinstimmen, ist das Programm gut ausgebildet.

Das Problem ist, dass, wenn der Compiler die Zeile bei [1] analysiert, kann es keine Art der Typüberprüfung durchführen, da es nicht weiß, was die Funktion ist. Wenn wir diese Zeile schreiben, irren wir die Reihenfolge der Argumente, und wir geben print("hi", 5); ein, der Compiler wird die Zeile dennoch akzeptieren, da er keine Vorkenntnisse von print hat. Da der Aufruf falsch ist, auch wenn der Code kompiliert wird, wird es später fehlschlagen.

Durch vorheriges Deklarieren der Funktion stellen Sie dem Compiler die erforderlichen Informationen zur Verfügung, die am Ort des Aufrufs überprüft werden müssen. Wenn die Deklaration vorhanden ist und der gleiche Fehler gemacht wird, erkennt der Compiler den Fehler und informiert Sie über Ihren Fehler.

In C++ hingegen wird der Compiler nicht davon ausgehen, dass der Aufruf korrekt ist und Sie tatsächlich eine Deklaration der Funktion vor dem Aufruf bereitstellen müssen.