2008-11-09 13 views
6

Sagen wir, ich habe ein C-Programm, das zu einer Reihe von * .c und * .h-Dateien gebrochen ist. Wenn Code aus einer Datei Funktionen aus einer anderen Datei verwendet, wo sollte ich die Header-Datei einfügen? In der * .c-Datei, die die Funktion verwendet, oder in der Kopfzeile dieser Datei?Wie zu strukturieren #includes in C

z. Datei foo.c enthält foo.h, die alle Deklarationen für foo.c enthält; dasselbe für bar.c und bar.h. Funktion foo1() innerhalb foo.c Aufrufe bar1(), die in bar.h deklariert und in bar.c definiert ist. Nun ist die Frage, sollte ich bar.h innerhalb foo.h, oder innerhalb foo.c einschließen?

Was wäre eine gute Reihe von Faustregeln für solche Probleme?

Antwort

6

Sie sollten foo.h inside foo.c. Auf diese Weise tragen andere c-Dateien, die foo.h enthalten, bar.h nicht unnötig. Dies ist mein Tipp für das Einfügen von Header-Dateien:

  • Fügen Sie Include-Definitionen in die c-Dateien ein - auf diese Weise werden die Dateiabhängigkeiten beim Lesen des Codes deutlicher.
  • Teilen Sie foo.h in zwei separate Dateien auf, sagen Sie foo_int.h und foo.h. Die erste enthält die Typ- und Forward-Deklarationen, die nur von foo.c benötigt werden. Das foo.h enthält die Funktionen und Typen, die von externen Modulen benötigt werden. Dies ist so etwas wie die private und öffentliche Sektion von foo.
  • Vermeiden Sie Querverweise, d. H. Foo Referenzen Bar und Bar Referenzen foo. Dies kann zu Verbindungsproblemen führen und ist auch ein Zeichen für ein schlechtes Design.
  • +3

    Der einzige Grund für das Erstellen einer Kopfzeile ist, weil Mehr als eine Quelldatei benötigt die Information. Der foo_int.h-Header sollte außerhalb von foo.h nicht benötigt werden, also sollte er nicht erstellt oder verwendet werden - sein Inhalt kann direkt in foo.c platziert werden. –

    +0

    Fortsetzung: Sie würden foo_int.h verwenden, wenn Sie mehr als eine Quelldatei mit der Funktionalität von 'foo.c' haben - sagen wir foo1.c und foo2.c. Dann macht der interne Header Sinn. Sie sollten jedoch darauf achten, die Dinge so kompakt zu halten, dass die C-Datei-Aufteilung unnötig ist. –

    +0

    Ich habe festgestellt, dass das Aufteilen der Dateien eine gute Technik ist, wenn Sie lange Typ-, Konstanten- oder MACRO-Definitionen haben, die nur für foo.c benötigt werden. Es ist besser, sie in einer Header-Datei als direkt in der Quelldatei zu haben. Adam Liss zeigte eine bessere Lösung an, alles in der gleichen .h Datei zu haben – kgiannakakis

    2

    Ich schließe die minimale Anzahl von möglichen Kopfzeilen in der Datei .h ein, und den Rest in die Datei .c. Dies hat den Vorteil, die Kompilierungszeiten manchmal zu reduzieren. In Ihrem Beispiel, wenn foo.h nicht unbedingt bar.h benötigt aber es trotzdem enthält, und einige andere Datei enthält foo.h, dann wird diese Datei neu kompiliert, wenn bar.h ändert, auch wenn es möglicherweise nicht benötigen oder bar.h verwenden.

    3

    Ich würde nur Header-Dateien in einer * .h-Datei enthalten, die für die Header-Datei selbst benötigt wird. Header-Dateien, die für eine Quelldatei benötigt werden, sollten meiner Meinung nach in der Quelldatei enthalten sein, damit die Abhängigkeiten von der Quelle offensichtlich sind. Header-Dateien sollten so erstellt werden, dass sie mit mehreren Einträgen umgehen können, sodass Sie sie bei Bedarf aus Gründen der Übersichtlichkeit einfügen können.

    2

    Die .h-Datei sollte die öffentliche Schnittstelle (alias die API) zu den Funktionen in der .c-Datei definieren.

    Wenn die Schnittstelle von file1 in die Schnittstelle von file2 dann #include file2.h verwendet file1.h

    Wenn die Implementierung in file1.c Verwendung von Sachen in file2.c macht, dann sollte file1.C# include file2.h.

    Ich muss zugeben, dass - weil ich immer file1.h in file1.c enthalten # - Ich würde normalerweise nicht stören # included file2.h direkt in file1.c, wenn es bereits in file1.h

    enthalten war

    Wenn Sie sich jemals in der Situation befinden, in der zwei .c-Dateien die anderen .h-Dateien enthalten, dann ist dies ein Zeichen dafür, dass die Modularität zusammengebrochen ist und Sie etwas über Umstrukturierungen nachdenken sollten.

    2

    Am Beispiel von foo.c und foo.h Ich habe diese Richtlinien gefunden hilfreich:

    • Denken Sie daran, dass der Zweck der foo.h ist die Verwendung von foo.c zu erleichtern, so dass es so einfach zu halten, organisiert und Selbst -erklärend wie möglich. Seien Sie liberal mit Kommentaren, die wie und erklären, wenn die Funktionen von foo.c verwenden - und wenn nicht, um sie zu verwenden.

    • foo.h erklärt die öffentlichen Funktionen von foo.c: Funktionen, Makros typedefs und (Schauder) globale Variablen.

    • foo.c sollte #include "foo.h - siehe Diskussion, und auch Jonathan Leffler Kommentar unten.

    • Wenn foo.c zusätzliche Header für die Kompilierung benötigt, fügen Sie sie in foo.c ein.

    • Wenn externe Header für foo.h erforderlich sind, zu kompilieren, sind sie in foo.h

    • Leverage den Präprozessor mehr enthalten als einmal zu verhindern foo.h aus ist. (Siehe unten.)

    • Wenn aus irgendeinem Grunde ein externer Kopf wird, um für eine weitere .c Datei benötigt werden, um die Funktionen zu verwenden, in foo.c, umfasst die Header in foo.h die nächsten Entwickler unnötige Fehlersuche zu speichern. Wenn Sie dies ablehnen, sollten Sie ein Makro hinzufügen, das Anweisungen zur Kompilierungszeit anzeigt, wenn die erforderlichen Header nicht enthalten sind.

    • nicht gehören eine .c Datei innerhalb einer anderen .c Datei, wenn Sie einen sehr guten Grund haben, und es klar dokumentieren.

    Wie kgiannakakis erwähnt, ist es hilfreich, sich nur innerhalb foo.c erforderlich, um die öffentliche Schnittstelle von den Definitionen und Erklärungen zu trennen. Aber anstatt zwei Dateien zu erstellen, ist es manchmal besser, der Präprozessor dies für Sie tun zu lassen:

    // foo.c 
    #define _foo_c_   // Tell foo.h it's being included from foo.c 
    #include "foo.h" 
    . . . 
    

     

    // foo.h 
    #if !defined(_foo_h_) // Prevent multiple inclusion 
    #define _foo_h_ 
    
    // This section is used only internally to foo.c 
    #ifdef _foo_c_ 
    . . . 
    #endif 
    
    // Public interface continues to end of file. 
    
    #endif // _foo_h_  // Last-ish line in foo.h 
    
    +0

    foo.c 'kann' einschließen foo.h - nein, foo.c muss foo.h einschließen, und als der erste Header, der eine automatische Überprüfung ist dieses foo.h ist in sich geschlossen und daher für andere nutzbar. –

    +0

    Außerdem gibt es in der Kopfzeile keinen Punkt, der Informationen enthält, die nur intern in der Implementierungsquelldatei verwendet werden. Die einzige Information, die in einen Header gelangt, sind öffentliche Dinge, die andere Dateien benötigen. –

    +0

    Danke ... Antwort aktualisiert, um Ihren Kommentar zu verweisen. –

    4

    Wie andere erwähnt haben, ein Kopf foo.h die Informationen erklären sollte notwendig in der Lage sein, die von einer Quelldatei foo.c. Dazu gehören die Typen, Aufzählungen und Funktionen von foo.c. (Sie verwenden keine globalen Variablen, oder? Wenn Sie das tun, werden diese auch in foo.h deklariert.)

    Der Header foo.h sollte in sich geschlossen und idempotent sein. Self-contained bedeutet, dass jeder Benutzer foo.h einschließen kann und sich nicht darum kümmern muss, welche anderen Header möglicherweise benötigt werden (da foo.h diese Header enthält). Idempotent bedeutet, dass wenn der Header mehrfach enthalten ist, kein Schaden entsteht.Dies wird durch die klassische Technik erreicht:

    #ifndef FOO_H_INCLUDED 
    #define FOO_H_INCLUDED 
    ...rest of the contents of foo.h... 
    #endif /* FOO_H_INCLUDED */ 
    

    Die Frage:

    Datei foo.c enthält foo.h, die alle Erklärungen für foo.c enthält; Gleiches für bar.c und bar.h. Die Funktion foo1() innerhalb von foo.c ruft bar1() auf, was in bar.h deklariert und in bar.c definiert ist. Jetzt ist die Frage, sollte ich bar.h innerhalb foo.h oder innerhalb foo.c einschließen?

    Es hängt davon ab, ob die von foo.h bereitgestellten Dienste von bar.h abhängig sind oder nicht. Wenn andere Dateien, die foo.h verwenden, einen der Typen oder Enumerationen benötigen, die von bar.h definiert sind, um die Funktionalität von foo.h zu verwenden, sollte foo.h sicherstellen, dass bar.h eingeschlossen ist (indem es eingeschlossen wird). Wenn jedoch die Dienste von bar.h nur in foo.c verwendet werden und nicht von denen, die foo.h verwenden, nicht benötigt werden, dann sollte foo.h bar.h nicht enthalten