Was ist der Vorteil für eine C-Quelldatei, die eine eigene Header-Datei enthält

Lesezeit: 7 Minuten

Benutzer-Avatar
Chen

Ich verstehe, dass eine Quelldatei, wenn sie auf Funktionen aus einer anderen Datei verweisen muss, ihre Header-Datei enthalten muss, aber ich verstehe nicht, warum die Quelldatei ihre eigene Header-Datei enthält. Inhalte in der Header-Datei werden einfach kopiert und in die Quelldatei als Funktionsdeklarationen pro Verarbeitungszeit eingefügt. Für Quelldateien, die ihre eigene Header-Datei enthalten, scheint mir eine solche “Deklaration” nicht notwendig zu sein, tatsächlich kompiliert und verknüpft das Projekt immer noch kein Problem, nachdem der Header aus der Quelldatei entfernt wurde. Was ist also der Grund für die Quelldatei? eigene Kopfzeile?

  • Hast du es versucht, ohne es einzufügen? Welche Fehlermeldungen hast du bekommen?

    – πάντα ῥεῖ

    13. Juni 2015 um 10:26 Uhr

  • Es ist ein Fehler, dem C-Compiler keine Gelegenheit zu geben, Ihnen mitzuteilen, dass es eine Diskrepanz zwischen der Deklaration und der Implementierung der Funktion gibt. Ohne diese Hilfe verlieren Sie mehrere Stunden Ihres Lebens, wenn Sie eine solche Diskrepanz entdecken. Es passiert zum denkbar ungünstigsten Zeitpunkt, in einem Jahr, wenn Sie sich nicht mehr so ​​gut an den Code erinnern und eine scheinbar unschuldige Änderung vornehmen. Machen Sie Ihr Programm auf eine sehr schwer zu diagnostizierende Weise explodieren. Es muss auf die harte Tour gelernt werden.

    – Hans Passant

    13. Juni 2015 um 10:41 Uhr

Der Hauptvorteil besteht darin, dass der Compiler die Konsistenz Ihres Headers und seiner Implementierung überprüft. Sie tun es, weil es bequem ist, nicht weil es erforderlich ist. Es kann durchaus möglich sein, das Projekt ohne eine solche Einbeziehung korrekt zu kompilieren und auszuführen, aber es erschwert auf lange Sicht die Wartung Ihres Projekts.

Wenn Ihre Datei keinen eigenen Header enthält, können Sie versehentlich in eine Situation geraten, in der die Vorwärtsdeklaration einer Funktion nicht mit der Definition der Funktion übereinstimmt – vielleicht weil Sie einen Parameter hinzugefügt oder entfernt und vergessen haben, den Header zu aktualisieren. Wenn dies geschieht, würde der Code, der sich auf die Funktion mit Nichtübereinstimmung stützt, immer noch kompiliert, aber der Aufruf würde zu einem undefinierten Verhalten führen. Es ist viel besser, den Compiler diesen Fehler abfangen zu lassen, was automatisch passiert, wenn Ihre Quelldatei einen eigenen Header enthält.

  • Können Sie mir sagen, wie der Compiler die Konsistenz überprüft? Es ist für mich in der Kompilierzeit magisch, wie der Compiler feststellen kann, ob die enthaltene Header-Datei seine eigene oder eine andere ist?

    – Chen

    13. Juni 2015 um 10:51 Uhr

  • @Chen Die Beziehung zwischen dem Header und dem .c Datei besteht darin, dass der Header Deklarationen für die Funktionen und globalen Variablen enthält, die die .c Datei definiert. Bei Funktionen enthält der Header zB Prototypen, die .c Datei enthält eine vollständige Definition; Der Compiler überprüft, ob die vollständige Definition dieselben Argument- und Rückgabetypen wie der Prototyp hat, den er zuvor beim Kompilieren des enthaltenen Headers gesehen hat.

    – Gilles ‘SO- hör auf, böse zu sein’

    13. Juni 2015 um 10:54 Uhr

  • @Chen Der Compiler hat keine Ahnung, dass der Header in irgendeiner Weise mit einer Quelldatei zusammenhängt. Es kommt nur darauf an, dass die Funktionsprototypen aus dem Header mit den Funktionsdefinitionen aus der Quelldatei übereinstimmen. Bei einer Nichtübereinstimmung wird ein Fehler ausgelöst.

    – Sergej Kalinitschenko

    13. Juni 2015 um 11:00 Uhr

  • @Chen Wenn Sie einen Header aus einer anderen Quelldatei einfügen, hat der Compiler nur Zugriff auf den Prototyp. Solange die tatsächlichen Parameter mit dem Prototyp übereinstimmen, wird sich der Compiler nicht beschweren, auch wenn die Implementierung anders ist. Der Compiler hat keine Möglichkeit, die Implementierung zu überprüfen, da sie sich in einer anderen Datei befindet. Wenn Sie den Header aus einer Datei einschließen, die Implementierungen der Funktionen enthält, hat der Compiler Zugriff auf den Prototyp und die Implementierung, sodass er sie gegeneinander prüfen kann. Stimmt der Prototyp nicht mit der tatsächlichen Definition überein, beschwert sich der Compiler.

    – Sergej Kalinitschenko

    13. Juni 2015 um 11:22 Uhr

  • @Chen Eine Nichtübereinstimmung zwischen einem Prototyp und seiner Implementierung wäre, wenn der Funktionsname gleich ist, aber der Rückgabetyp und/oder die formalen Parametertypen unterschiedlich sind. Name Mangling hätte Fehler in solchen Situationen verhindern können, da der Linker eine fehlende Funktion erkennen würde, aber wie Sie in Ihrem Kommentar richtig angemerkt haben, hat C kein Name Mangling.

    – Sergej Kalinitschenko

    13. Juni 2015 um 12:06 Uhr

Die Header-Datei sagt den Leuten, was die Quelldatei kann.

Die Quelldatei für die Header-Datei muss also ihre Verpflichtungen kennen. Deshalb ist es enthalten.

  • Ich befürworte es, aber ich bin immer noch ein bisschen neidisch auf deine Prägnanz 🙂

    – LSerni

    13. Juni 2015 um 10:55 Uhr

  • Ich bin sicher, Sie können es herunterladen

    – Ed heilen

    13. Juni 2015 um 11:09 Uhr

  • Ähm, ich fürchte, ich habe es nicht verstanden – Englisch ist nicht meine Muttersprache. Was meinst du mit lade es herunter?

    – LSerni

    13. Juni 2015 um 11:15 Uhr

  • Sie können meine Prägnanz herunterladen. Es ist eine sehr kleine Datei.

    – Ed heilen

    13. Juni 2015 um 11:22 Uhr


Praxisbeispiel – Angenommen in einem Projekt sind folgende Dateien:

/* foo.h */
#ifndef FOO_H
#define FOO_H
double foo( int x );
#endif

/* foo.c */
int foo( int x )
{
  ...
}

/* main.c */
#include "foo.h"

int main( void )
{
  double x = foo( 1 );
  ...
}

Beachten Sie, dass die Deklaration infoo.h entspricht nicht der Definition in foo.c; Die Rückgabetypen sind unterschiedlich. main.c ruft die foo Funktion unter der Annahme, dass sie a zurückgibt doublegemäß der Erklärung in foo.h.

foo.c und main.c werden getrennt voneinander zusammengestellt. Seit main.c Anrufe foo wie erklärt in foo.h, es wird erfolgreich kompiliert. Seit foo.c tut nicht enthalten foo.hist sich der Compiler des Typkonflikts zwischen der Deklaration und der Definition nicht bewusst, sodass er ebenfalls erfolgreich kompiliert wird.

Wenn Sie die beiden Objektdateien miteinander verknüpfen, stimmt der Maschinencode für den Funktionsaufruf nicht mit dem überein, was der Maschinencode für die Funktionsdefinition erwartet. Der Funktionsaufruf erwartet a double zurückzugebender Wert, aber die Funktionsdefinition gibt einen zurück int. Dies ist ein Problem, insbesondere wenn die beiden Typen nicht die gleiche Größe haben. Im besten Fall erhalten Sie ein Garbage-Ergebnis.

Durch Einbeziehung foo.h in foo.ckann der Compiler diesen Konflikt erkennen, bevor Sie Ihr Programm ausführen.

Und, wie in einer früheren Antwort darauf hingewiesen wird, wenn foo.h definiert alle Typen oder Konstanten, die von verwendet werden foo.cdann müssen Sie es unbedingt einschließen.

Benutzer-Avatar
LSerni

Ihre scheint ein Grenzfall zu sein, aber eine Include-Datei kann als eine Art von angesehen werden Vertrag zwischen dieser Quelldatei und allen anderen Quelldateien, die diese Funktionen benötigen könnten.

Indem Sie den “Vertrag” in eine Header-Datei schreiben, können Sie sicherstellen, dass die anderen Quelldateien wissen, wie diese Funktionen aufgerufen werden, oder besser gesagt, Sie werden sicher sein, dass die Compiler fügt den richtigen Code ein und überprüft seine Gültigkeit zur Kompilierzeit.

Was aber, wenn Sie dann (auch versehentlich) den Funktionsprototyp in der entsprechenden Quelldatei geändert haben?

Indem Sie denselben Header wie alle anderen in diese Datei aufnehmen, werden Sie zur Kompilierzeit gewarnt, falls eine Änderung versehentlich den Vertrag “bricht”.

Update (aus dem Kommentar von @tmlen): Auch wenn dies in diesem Fall nicht der Fall ist, eine Include-Datei kann Verwenden Sie auch Deklarationen und Pragmas wie #defines, typedef, enum, struct und inline sowie Compiler-Makros, deren mehrfaches Schreiben keinen Sinn machen würde (eigentlich wären das gefährlich an zwei verschiedenen Stellen zu schreiben, damit die Kopien nicht mit katastrophalen Folgen nicht mehr synchron sind). Einige davon (z Pragma zum Auffüllen von Strukturen) könnten zu Fehlern werden, die schwer aufzuspüren sind.

Benutzer-Avatar
Ely

Dies ist nützlich, da Funktionen deklariert werden können, bevor sie definiert werden.

Es kommt also vor, dass Sie die Deklaration haben, gefolgt von einem Aufruf\Aufruf, gefolgt von der Implementierung. Du musst nicht, aber du kannst.

Die Header-Datei enthält die Deklarationen. Sie können jederzeit aufrufen, solange der Prototyp übereinstimmt. Und solange der Compiler eine Implementierung findet, bevor er die Kompilierung beendet.

1178710cookie-checkWas ist der Vorteil für eine C-Quelldatei, die eine eigene Header-Datei enthält

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

Privacy policy