Eine C-Quelldatei in eine andere einfügen?

Lesezeit: 7 Minuten

Ist es in Ordnung (oder sogar empfohlen/gute Praxis) zu #include a .c Datei in einem anderen .c Datei?

Richtig eingesetzt, kann dies eine nützliche Technik sein.

Angenommen, Sie haben ein komplexes, leistungskritisches Subsystem mit einer relativ kleinen öffentlichen Schnittstelle und viel nicht wiederverwendbarem Implementierungscode. Der Code umfasst mehrere tausend Zeilen, etwa hundert private Funktionen und ziemlich viele private Daten. Wenn Sie mit nicht-trivialen eingebetteten Systemen arbeiten, werden Sie wahrscheinlich häufig genug mit dieser Situation konfrontiert.

Ihre Lösung wird wahrscheinlich geschichtet, modular und entkoppelt sein, und diese Aspekte können sinnvoll dargestellt und verstärkt werden, indem Sie verschiedene Teile des Subsystems in verschiedenen Dateien codieren.

Mit C können Sie dadurch viel verlieren. Fast alle Toolchains bieten eine anständige Optimierung für eine einzelne Kompilierungseinheit, sind aber sehr pessimistisch in Bezug auf alles, was als extern deklariert wird.

Wenn Sie alles in ein C-Quellmodul stecken, erhalten Sie –

  • Verbesserungen bei Leistung und Codegröße – Funktionsaufrufe werden in vielen Fällen eingebettet. Auch ohne Inlining hat der Compiler Möglichkeiten, effizienteren Code zu produzieren.

  • Verbergen von Daten und Funktionen auf Verbindungsebene.

  • Vermeidung von Namespace-Verschmutzung und deren Folgen – Sie können weniger unhandliche Namen verwenden.

  • Schnellere Kompilierung und Verknüpfung.

Aber Sie bekommen auch ein unheiliges Durcheinander, wenn es darum geht, diese Datei zu bearbeiten, und Sie verlieren die implizite Modularität. Dies kann überwunden werden, indem die Quelle in mehrere Dateien aufgeteilt und diese zu einer einzigen Kompilationseinheit zusammengefügt werden.

Sie müssen jedoch einige Konventionen auferlegen, um dies ordnungsgemäß zu verwalten. Diese hängen bis zu einem gewissen Grad von Ihrer Toolchain ab, aber einige allgemeine Hinweise sind –

  • Setzen Sie die öffentliche Schnittstelle in eine separate Header-Datei – Sie sollten dies sowieso tun.

  • Haben Sie eine Haupt-.c-Datei, die alle untergeordneten .c-Dateien enthält. Dies könnte auch den Code für die öffentliche Schnittstelle beinhalten.

  • Verwenden Sie Compiler Guards, um sicherzustellen, dass private Header und Quellmodule nicht von externen Kompilierungseinheiten eingeschlossen werden.

  • Alle privaten Daten und Funktionen sollten als statisch deklariert werden.

  • Behalten Sie die konzeptionelle Unterscheidung zwischen .c- und .h-Dateien bei. Dabei werden bestehende Konventionen genutzt. Der Unterschied besteht darin, dass Sie viele statische Deklarationen in Ihren Headern haben werden.

  • Wenn Ihre Toolchain keinen Grund dagegen vorschreibt, benennen Sie die privaten Implementierungsdateien als .c und .h. Wenn Sie Include-Guards verwenden, erzeugen diese keinen Code und führen keine neuen Namen ein (Sie können während der Verknüpfung mit einigen leeren Segmenten enden). Der große Vorteil ist, dass andere Tools (z. B. IDEs) diese Dateien entsprechend behandeln.

  • +1 Dies ist immer noch die Realität, während bessere Compiler diese Methode mit der Zeit überflüssig machen. GCC 4.5 mit Link-Time-Optimierung ist ein großer Schritt auf diesem Weg.

    – u0b34a0f6ae

    10. Juli 2010 um 9:59 Uhr

  • Ich habe so viele Programme gesehen, die das tun, und es geht mir auf die Nerven, wenn ich versuche, ihren Code wiederzuverwenden. Vielen Dank für die Erklärung, warum sie es tun, und für den Vorschlag, einen Wächter zu verwenden (was oft nicht getan wird).

    – Joey Adams

    19. Juli 2011 um 8:15 Uhr

  • Bei der Embedded-Entwicklung für C51 hat die Verwendung externer und mehrerer C-Dateien nur Kopfschmerzen verursacht. Ich wechsle zu einer C-Datei, die alle anderen enthält, um meine Zeit zurückzubekommen.

    – Mahesh

    23. Februar 2017 um 21:23 Uhr

  • Für die Aufzeichnungen: GCC unterstützt die Optimierung über verschiedene Übersetzungseinheiten hinweg, siehe diesen SO-Thread und den GCC-Handbuchabschnitt weiter Linkzeitoptimierung.

    – Twonki

    5. Mai 2018 um 15:51 Uhr

  • Auch fürs Protokoll: Die Linkzeitoptimierung ist von Natur aus schwieriger, es sei denn, Sie können dem Compiler irgendwie alle Informationen aus den C-Quelldateien zur Verfügung stellen, die mit dem Code in den zu verknüpfenden Objektdateien verknüpft sind (was im Wesentlichen der obige Link beschreibt, wie GCC es tut ). Damit andere Entwickler von Ihren verteilten Objektdateien profitieren können, erfordert LTO größere Objektdateien, um die gleichen Optimierungen zu erreichen. Je nachdem, wie LTO implementiert wird, kann es wahrscheinlich nicht 100 % der Optimierungen erreichen, die die Optimierung innerhalb einer einzelnen Übersetzungseinheit erreichen kann. LTO kann Builds auch langsamer machen.

    – mtraceur

    1. März 2021 um 3:24 Uhr

Ist es o.k? ja, es wird kompiliert

ist es empfehlenswert? nein – .c-Dateien werden zu .obj-Dateien kompiliert, die nach der Kompilierung (durch den Linker) in die ausführbare Datei (oder Bibliothek) miteinander verknüpft werden, sodass keine .c-Datei in eine andere eingefügt werden muss. Was Sie stattdessen wahrscheinlich tun möchten, ist, eine .h-Datei zu erstellen, die die Funktionen/Variablen auflistet, die in der anderen .c-Datei verfügbar sind, und die .h-Datei einzuschließen

  • Beachten Sie auch, dass es möglicherweise nicht verknüpft wird, selbst wenn es kompiliert wird, wenn die #included .c-Datei ebenfalls kompiliert und die beiden Objektdateien miteinander verknüpft werden – Sie könnten am Ende mehrfach definierte Symbole erhalten.

    – Nik Meyer

    10. Juli 2009 um 12:50 Uhr

  • Eine kleine Frage. Ich habe eine Header-Datei, die eine struct + -Methode deklariert, und auch die entsprechende .c-Datei, um sie zu definieren. Wenn diese Methode die Struktur als Parameter verwendet, wie würde ich vermeiden, die .c-Datei in eine andere .c-Datei aufzunehmen, in der die Hauptmethode definiert ist?

    – stdout

    9. März 2019 um 13:23 Uhr

Nein.

Abhängig von Ihrer Build-Umgebung (die Sie nicht angeben) stellen Sie möglicherweise fest, dass sie genau so funktioniert, wie Sie es möchten.

Es gibt jedoch viele Umgebungen (sowohl IDEs als auch viele handgefertigte Makefiles), die erwarten, *.c zu kompilieren – wenn das passiert, werden Sie wahrscheinlich mit Linker-Fehlern aufgrund doppelter Symbole enden.

In der Regel sollte diese Praxis vermieden werden.

Wenn Sie den Quellcode unbedingt #einschließen müssen (was im Allgemeinen vermieden werden sollte), verwenden Sie ein anderes Dateisuffix für die Datei.

Ich dachte, ich würde eine Situation teilen, in der mein Team beschlossen hat, .c-Dateien einzuschließen. Unsere Architektur besteht größtenteils aus Modulen, die durch ein Nachrichtensystem entkoppelt sind. Diese Nachrichtenhandler sind öffentlich und rufen viele lokale statische Worker-Funktionen auf, um ihre Arbeit zu erledigen. Das Problem trat auf, als wir versuchten, Abdeckung für unsere Unit-Testfälle zu erhalten, da die einzige Möglichkeit, diesen privaten Implementierungscode auszuführen, indirekt über die öffentliche Nachrichtenschnittstelle war. Da einige Worker-Funktionen knietief im Stack steckten, erwies sich dies als Albtraum, um eine angemessene Abdeckung zu erreichen.

Das Einbinden der .c-Dateien gab uns eine Möglichkeit, das Zahnrad in der Maschine zu erreichen, die wir für den Test interessant fanden.

Sie können den gcc-Compiler unter Linux verwenden, um zwei c-Dateien in einer Ausgabe zu verknüpfen. Angenommen, Sie haben zwei c-Dateien, eine ist „main.c“ und die andere „support.c“. Der Befehl, diese beiden zu verknüpfen, lautet also

gcc main.c support.c -o main.out

Dadurch werden zwei Dateien zu einer einzigen Ausgabe verknüpft. main.out Um die Ausgabe auszuführen, wird der Befehl verwendet

./main.out

Wenn Sie eine Funktion in main.c verwenden, die in der Datei support.c deklariert ist, sollten Sie sie in main auch mit der externen Speicherklasse deklarieren.

Benutzeravatar von HS
HS.

Die Erweiterung der Datei spielt für die meisten C-Compiler keine Rolle, daher wird es funktionieren.

Abhängig von Ihren Makefile- oder Projekteinstellungen kann die enthaltene c-Datei jedoch eine separate Objektdatei generieren. Beim Verlinken kann das zu doppelt definierten Symbolen führen.

Benutzeravatar von MikeyPro
MikeyPro

Sie können .C- oder .CPP-Dateien ordnungsgemäß in andere Quelldateien einfügen. Abhängig von Ihrer IDE können Sie normalerweise doppelte Verknüpfungen verhindern, indem Sie sich die Eigenschaften der Quelldatei ansehen, die Sie einbeziehen möchten, normalerweise indem Sie mit der rechten Maustaste darauf klicken und auf Eigenschaften klicken und das Kontrollkästchen zum Kompilieren/Verknüpfen/Ausschließen von Build oder einer anderen Option deaktivieren/aktivieren vielleicht. Oder Sie könnten die Datei nicht in das Projekt selbst aufnehmen, sodass die IDE nicht einmal weiß, dass sie existiert, und nicht versucht, sie zu kompilieren. Und mit Makefiles würden Sie die Datei zum Kompilieren und Linken einfach nicht hineinlegen.

BEARBEITEN: Entschuldigung, ich habe es zu einer Antwort anstelle einer Antwort auf andere Antworten gemacht 🙁

1423870cookie-checkEine C-Quelldatei in eine andere einfügen?

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

Privacy policy