Zweck von C/C++-Prototypen

Lesezeit: 9 Minuten

Benutzer-Avatar
Python-Neuling

Ich habe Wikipedia über C/C++-Prototyp-Anweisungen gelesen und bin verwirrt:

Wikipedia sagt: “Indem Sie den Funktionsprototypen einschließen, informieren Sie den Compiler, dass die Funktion “fac” ein ganzzahliges Argument akzeptiert, und Sie ermöglichen dem Compiler, diese Art von Fehlern abzufangen.”

und verwendet das Folgende 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 die Funktionsdefinition der aufgerufenen Funktion beinhaltet bereits alle die Informationen, die der Prototyp dem Compiler mitteilt, also warum kann der Compiler diese Informationen nicht aus der Definition der aufgerufenen Funktion ableiten, da sie sie enthalten identisch Erklärungen/Informationen Brief für Brief?

Was vermisse ich? Scheint wie zusätzliche Arbeit ohne 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, einige Kludges zu benötigen, um die Dinge in einem einzigen Durchgang genau zu erledigen. Es scheint mir jetzt offensichtlicher. Anscheinend erfordert es ziemlich genaue Kenntnisse darüber, wie der Compiler verknüpft und kompiliert.

  • 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. Das habe ich aufgegeben.

    – Johannes Schaub – litb

    21. September 2010 um 20:05 Uhr

  • Aus diesem Grund definiere ich Funktionen, bevor sie aufgerufen werden (zumindest in derselben Quelldatei). Dadurch entfällt die Notwendigkeit einer separaten Deklaration, obwohl dies bedeutet, dass mein Code “rückwärts” gelesen wird.

    – Johannes Bode

    21. September 2010 um 21:54 Uhr

Benutzer-Avatar
Tyler McHenry

Zwei Gründe:

  1. Der Compiler liest die Datei von oben nach unten. Wenn fac verwendet wird main was oben ist facund kein Prototyp vorhanden ist, weiß der Compiler nicht, wie er überprüfen soll, ob dieser Aufruf korrekt ausgeführt wird, da er noch nicht die Definition von erreicht hat fac.

  2. Es ist möglich, ein C- oder C++-Programm in mehrere Dateien aufzuteilen. fac kann in einer völlig anderen Datei als der Datei definiert sein, 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 von Ihnen geposteten Beispiel nur für C gelten. In C++ gilt dieses Beispiel stets einen Fehler erzeugen, selbst wenn der Prototyp weggelassen wird (obwohl es einen anderen Fehler erzeugen wird, je nachdem, ob der Prototyp vorhanden ist oder nicht). In C++ müssen alle Funktionen definiert oder prototypisiert werden, bevor sie verwendet werden.

In C können Sie den Prototyp weglassen, und der Compiler erlaubt Ihnen, die Funktion mit einer beliebigen Anzahl von Argumenten (einschließlich Null) aufzurufen, und nimmt einen Rückgabetyp von an int. Aber nur weil es Sie während der Kompilierung nicht anschreit, heißt das nicht, dass das Programm richtig funktioniert, wenn Sie die Funktion nicht richtig aufrufen. Aus diesem Grund ist es nützlich, einen Prototyp in C zu erstellen, damit der Compiler dies in Ihrem Namen überprüfen kann.

Die Philosophie hinter C und C++, die diese Art von Feature motiviert, ist, dass es sich um relativ niedrige Sprachen handelt. Sie machen nicht viel Händchenhalten und sie tun nicht viel, wenn überhaupt Laufzeitüberprüfungen. Wenn Ihr Programm etwas falsch macht, stürzt es ab oder verhält sich seltsam. Daher enthalten die Sprachen Funktionen wie diese, die es dem Compiler ermöglichen, bestimmte Arten von Fehlern zur Kompilierzeit zu identifizieren, sodass Sie sie leichter finden und beheben können.

  • Falls Nr. 2 keine Includes oder ähnliches mitteilen würde, in welcher Datei die eigentliche Funktion zu finden ist? In Python importieren Sie einfach das Modul und verweisen dann auf die Funktion als module.function, hat c keine Namespaces, auf die so verwiesen werden kann?

    – Python-Neuling

    21. September 2010 um 20:04 Uhr


  • Wie löst es #1? Nichts im Prototyp sagt dem Compiler, dass fac main aufruft und/oder dass main fac aufruft?

    – Python-Neuling

    21. September 2010 um 20:05 Uhr


  • Was denkst du in Fall Nr. 2, was enthalten die Includes? Sie enthalten Prototypen! Aber wenn du selbst schreibst fac Funktion, Sie Sie müssen den Prototyp bereitstellen, sei es in einer separaten eingebundenen Datei oder in derselben Quelldatei. In Fall Nr. 1 (ich nehme an, Sie beziehen sich hier auf die Antwort von Mystagogue) ist das Problem nicht, dass mit einer rekursiven Abhängigkeit etwas nicht stimmt, sondern dass es keine Möglichkeit gibt, die beiden Funktionen zu ordnen, wenn Sie eine rekursive Abhängigkeit haben so dass der Compiler die Definition von beiden liest, bevor er einen Aufruf an einen von beiden verarbeitet.

    – Tyler McHenry

    21. September 2010 um 20:08 Uhr


  • Die Deklaration einer Funktion befindet sich oft in einer Header-Datei und ihre Implementierung in einer Quelldatei. Die Quelldatei wird zu einer Objektdatei kompiliert. Andere Quelldateien oder Programme, die die Funktion verwenden möchten, fügen einfach den Header ein und verknüpfen dann die Objektdatei. Auf diese Weise muss die Funktion nur einmal kompiliert werden, kann aber mehrfach in vielen Quelldateien und Programmen verwendet werden.

    – Björn Pollex

    21. September 2010 um 20:08 Uhr

  • @pythonnewbie: Beide (und viele weitere) Probleme lassen sich jetzt leicht lösen, aber denken Sie daran, dass C aus den 70er Jahren stammt. Sicher, die Sprache hätte einen Multi-Pass-Compiler erfordern können, der Zugriff auf alle verwendeten hat .cpp Dateien (im Gegensatz zu den Headern nur mit den Prototypen und den bereits kompilierten Objektdateien mit der Implementierung), damit es Funktionssignaturen und alles ableiten kann. Aber das war damals verdammt teuer, also haben sie es nicht gemacht.

    Benutzer395760

    21. September 2010 um 20:09 Uhr


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

In Ihrem Beispiel befindet sich der gesamte Code in einer Datei, und Sie hätten die fac()-Definition genauso gut dorthin verschieben können, wo sich der Prototyp derzeit befindet, und den Prototyp entfernen.

Reale Programme bestehen aus mehreren .cpp-Dateien (auch bekannt als Kompilierungseinheiten), die häufig kompiliert und in Bibliotheken gelinkt werden, bevor sie in die endgültige ausführbare Form gelinkt werden. Bei Großprojekten dieser Art werden Prototypen in .h-Dateien (auch Header-Dateien genannt) gesammelt, wobei der Header zur Kompilierzeit in andere Kompilierungseinheiten eingefügt wird, um den Compiler auf das Vorhandensein und die Aufrufkonventionen der Funktionalität in der Bibliothek aufmerksam zu machen. In diesen Fällen steht die Funktionsdefinition dem Compiler nicht zur Verfügung, sodass die Prototypen (auch bekannt als Deklarationen) als eine Art Vertrag dienen, der die Fähigkeiten und Anforderungen der Bibliothek definiert.

Der wichtigste Grund für den Prototyp ist die Auflösung von zirkulären Abhängigkeiten. Wenn “main” “fac” aufrufen kann und “fac” “main” aufruft, benötigen Sie einen Prototyp, um dies zu lösen.

  • Dies, zusammen mit dem Linken zu einer bereits kompilierten Bibliothek, sind wirklich die beiden Gründe, warum eine Deklaration erforderlich ist. Alle anderen sind Kosmetik/Präferenzen.

    – Codeschimpanse

    13. November 2015 um 20:00 Uhr

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 das tut, was Sie erwarten können: 5 Zeilen mit der Aufschrift “hi” in jeder von ihnen ausgeben. Die C-Sprache findet den Aufruf bei [1] es setzt das voraus print ist eine Funktion, die zurückkehrt int und nimmt eine unbekannte Anzahl von Argumenten (unbekannt für den Compiler, bekannt für den Programmierer), den Compiler geht davon aus dass der Aufruf korrekt ist und mit dem Kompilieren fortfährt. Da die Funktionsdefinition und der Aufruf übereinstimmen, ist das Programm wohlgeformt.

Das Problem ist, dass, wenn der Compiler die Zeile bei analysiert [1] es kann keine Typüberprüfung durchführen, da es die Funktion nicht kennt. Wenn wir beim Schreiben dieser Zeile die Reihenfolge der Argumente verwechseln und tippen print( "hi", 5 ); Der Compiler akzeptiert die Zeile trotzdem, da er keine Vorkenntnisse hat print. Da der Aufruf auch dann falsch ist, wenn der Code kompiliert wird, schlägt er später fehl.

Indem Sie die Funktion vorher deklarieren, geben Sie dem Compiler die notwendigen Informationen zur Überprüfung an der Aufrufstelle an die Hand. Wenn die Deklaration vorhanden ist und derselbe Fehler gemacht wird, erkennt der Compiler den Fehler und informiert Sie über Ihren Fehler.

In C++ hingegen geht der Compiler nicht davon aus, dass der Aufruf korrekt ist und wird es tatsächlich tun benötigen Sie müssen vor dem Aufruf eine Deklaration der Funktion bereitstellen.

Der C-Compiler verarbeitet Quelldateien von oben nach unten. Funktionen, die angezeigt werden nach ihre Verwendung wird beim Auflösen von Argumenttypen nicht berücksichtigt. Also, in Ihrem Beispiel, wenn main() am Ende der Datei wäre, dann bräuchte man keinen Prototyp dafür fac() (seit der Definition von fac() bereits beim Kompilieren vom Compiler gesehen worden wäre main()).

Benutzer-Avatar
Lorenz

Abgesehen von all den guten Antworten, die bereits gegeben wurden, denken Sie darüber nach: Wenn Sie ein Compiler wären und Ihre Aufgabe darin bestand, Quellcode in Maschinensprache zu übersetzen, und Sie (als pflichtbewusster Compiler, der Sie sind) einen Quellcode nur Zeile für Zeile lesen könnten — Wie würden Sie den eingefügten Code lesen, wenn es keinen Prototyp gäbe? Woher wissen Sie, dass der Funktionsaufruf gültig und kein Syntaxfehler ist? (Ja, man könnte sich eine Notiz machen und am Ende prüfen, ob alles zusammenpasst, aber das ist eine andere Geschichte).

Eine andere Betrachtungsweise (diesmal als Mensch): Stell dir vor unterlassen Sie die Funktion als Prototyp definiert haben, Noch ist sein Quellcode verfügbar. Sie wissen jedoch, dass sich in der Bibliothek, die Ihr Kumpel Ihnen gegeben hat, der Maschinencode befindet, der, wenn er ausgeführt wird, ein bestimmtes erwartetes Verhalten zurückgibt. Wie schön. Nun, wie würde Ihr Compiler wissen, dass ein solcher Funktionsaufruf gültig ist, wenn es keinen Prototyp gibt, der ihm sagt: “Hey Alter, vertrau mir, es gibt eine Funktion mit dem Namen so und so, die Parameter akzeptiert und etwas zurückgibt”?

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

1365180cookie-checkZweck von C/C++-Prototypen

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy