C-Header-Dateien und Kompilierung/Linking

Lesezeit: 7 Minuten

Benutzeravatar von Aristides
Aristides

Ich weiß, dass Header-Dateien Vorwärtsdeklarationen verschiedener Funktionen, Strukturen usw. haben, die in der verwendet werden .c Datei, die die ‘aufruft’ #include, Rechts? Soweit ich verstehe, erfolgt die “Gewaltenteilung” wie folgt:

Header-Datei: func.h

  • enthält Vorwärtsdeklaration der Funktion

    int func(int i);
    

C-Quelldatei: func.c

  • enthält die eigentliche Funktionsdefinition

    #include "func.h"
    
    int func(int i) {
        return ++i ;
    }
    

C-Quelldatei source.c (das “eigentliche” Programm):

#include <stdio.h>
#include "func.h"

int main(void) {
    int res = func(3);
    printf("%i", res);
}

Meine Frage ist: Sehen, dass die #include ist einfach eine Compiler-Direktive, die den Inhalt der kopiert .h in der Datei das #include ist in, wie funktioniert die .c Datei wissen, wie die Funktion tatsächlich ausgeführt wird? Alles, was es bekommt, ist die int func(int i);, wie kann es also die Funktion tatsächlich ausführen? Wie erhält es Zugang zur eigentlichen Definition von func? Enthält der Header eine Art „Zeiger“, der sagt: „Das ist meine Definition, da drüben!“?

Wie funktioniert es?

  • Das ist die Magie von Linker die Definitionen aufzulösen und sicherzustellen, dass die Dinge, von denen Sie während der Kompilierung behaupteten, dass sie existieren, tatsächlich existieren.

    – Uchia Itachi

    31. August 2013 um 12:35 Uhr

  • Wenn Sie sich mit Header-Dateien befassen, möchten Sie vielleicht etwas darüber lesen gehören Wächter.

    – Irgendein Programmierer-Typ

    31. August 2013 um 12:36 Uhr

  • Ich kenne Include-Wächter (ifndef und all das), habe sie aber der Kürze halber weggelassen.

    – Aristides

    31. August 2013 um 12:42 Uhr

  • Mir scheint, Sie denken, dass die Quellcode verwendet wird, um das Programm auszuführen, wie in Skriptsprachen (JavaScript usw.). Das ist nicht der Fall. C-Quellcode wird zunächst (vom Compiler und Linker) in Maschinencode umgewandelt, der dann direkt von der Prozessorhardware ausgeführt wird.

    – zentrunix

    31. August 2013 um 13:10 Uhr

  • Was meinst du mit “der Quellcode wird verwendet, um das Programm auszuführen”? C (oder die GCC-Implementierung) wird kompiliert, nicht interpretiert. Ich weiß, dass es im Voraus in Maschinencode kompiliert wird. Es ist der Quellcode, der verwendet wird, um den Maschinencode zu generieren. Ich bin mir nicht sicher, was Ihr Punkt ist.

    – Aristides

    31. August 2013 um 13:20 Uhr


Uchia Itachi gab die Antwort. Es ist das Linker.

Verwendung des GNU C-Compilers gcc Sie würden ein Ein-Datei-Programm wie kompilieren

gcc hello.c -o hello # generating the executable hello

Aber um das Programm mit zwei (oder mehr) Dateien wie in Ihrem Beispiel beschrieben zu kompilieren, müssten Sie Folgendes tun:

gcc -c func.c # generates the object file func.o
gcc -c main.c # generates the object file main.o
gcc func.o main.o -o main # generates the executable main

Jede Objektdatei hat externe Symbole (Sie können sie sich als öffentliche Elemente vorstellen). Funktionen sind standardmäßig extern, während (globale) Variablen standardmäßig intern sind. Sie könnten dieses Verhalten ändern, indem Sie definieren

static int func(int i) { # static linkage
    return ++i ;
}

oder

/* global variable accessible from other modules (object files) */
extern int global_variable = 10; 

Wenn ein Aufruf einer Funktion auftritt, die nicht im Hauptmodul definiert ist, durchsucht der Linker alle Objektdateien (und Bibliotheken), die als Eingabe für das Modul bereitgestellt werden, in dem die aufgerufene Funktion definiert ist. Standardmäßig haben Sie wahrscheinlich einige Bibliotheken mit Ihrem Programm verknüpft, so können Sie es verwenden printfes ist bereits in eine Bibliothek kompiliert.

Wenn Sie wirklich interessiert sind, versuchen Sie es mit Assembler-Programmierung. Diese Namen entsprechen Labels im Assemblercode.

  • Bei GCC ist das Muster also: 1. Verwenden Sie das Flag -c mit jeder .c (mit Definitionen) und .h (mit Funktionsprototypen), um jede .o zu erstellen. 2. Verwenden Sie das Flag -o und jede .o-Datei, um die endgültige Datei zu erstellen exe ?

    – Aristides

    31. August 2013 um 14:05 Uhr

  • Ja. Die Option “-c” steht für “kompilieren”, also nur um den Objektcode in Objektdateien zu kompilieren. gcc ohne -c erkennt, dass die Eingaben Objektdateien sind, also verknüpft es sie einfach mit dem Linker. Und schließlich ist das Flag -o optional, es wird verwendet, um den Namen der Ausgabedatei der ausführbaren Datei anzugeben.

    – Emil Vatai

    31. August 2013 um 14:34 Uhr

Es ist der Linker, der all das handhabt. Der Compiler gibt einfach eine spezielle Sequenz in der Objektdatei aus, die besagt: „Ich habe dieses externe Symbol funcbitte lösen Sie es” für den Linker. Dann sieht der Linker das und durchsucht alle anderen Objektdateien und Bibliotheken nach dem Symbol.

  • Bedeutet das alles .c Datei im Projekt wird gesucht ?

    – Lidong Guo

    31. August 2013 um 12:50 Uhr

  • @LidongGuo Wenn Sie alle Quelldateien auf der Befehlszeile zusammen kompilieren oder Objektdateien aus allen Quellen erstellen und alle miteinander verknüpfen, werden sie ja durchsucht. Dies geschieht jedoch nicht automatisch, Sie müssen dem Linker mitteilen, welche Objektdateien Sie verknüpfen möchten, und nur diese werden durchsucht.

    – Irgendein Programmierer-Typ

    31. August 2013 um 12:55 Uhr

  • @Someprogrammerdude, aber woher weiß der Linker aus der .o-Datei, welche Funktionen unter welchem ​​Header exportiert werden und welche Funktionen unter welchem ​​Header nicht aufgelöst werden, ist der Header-Dateiname oder eine Art Hash in der Symboltabelle gespeichert, damit der Linker dies sicherstellt es stimmt überein (dh dass der Header in beiden Dateien enthalten ist und nur eine Objektdatei exportiert wird)

    – Lewis Kelsey

    4. April 2019 um 9:47 Uhr

  • @LewisKelsey Header-Dateien spielen beim Verknüpfen keine Rolle. Eine Objektdatei ist im Grunde eine einzelne Übersetzungseinheit, in zusammengestellter Form. Der Linker weiß, wo ein Symbol definiert ist, da Objektdateien auch die in der Übersetzungseinheit definierten Symbole enthalten. Wenn Sie mehr Details über die Funktionsweise von Linkern wünschen, schlage ich vor, dass Sie in Ihrer bevorzugten Suchmaschine nach Informationen darüber suchen, da es ein viel zu breites Thema ist, um es hier zu beantworten (insbesondere in Kommentaren).

    – Irgendein Programmierer-Typ

    4. April 2019 um 9:54 Uhr

Eine Deklaration eines Symbols ohne Definition innerhalb derselben Kompilierungseinheit weist den Compiler an, mit einem Platzhalter für die Adresse dieses Symbols in eine Objektdatei zu kompilieren.

Der Linker erkennt, dass eine Definition für das Symbol erforderlich ist, und sucht nach externen Definitionen des Symbols in Bibliotheken und anderen Objektdateien.

Wenn der Linker eine Definition findet, wird der Platzhalter in der ursprünglichen Objektdatei durch die gefundene Adresse in der endgültigen ausführbaren Datei ersetzt.

Der Header bietet Zugriff nicht nur auf andere .c Dateien im selben Programm, aber auch in Bibliotheken, die möglicherweise in Binärform verteilt werden. Die Beziehung von einem .c Datei zu einer anderen ist genau dasselbe wie eine Bibliothek, die von einer anderen abhängt.

Da eine Programmierschnittstelle unabhängig vom Format der Implementierung in Textform vorliegen muss, sind Header-Dateien als Trennung sinnvoll.

Wie andere bereits erwähnt haben, wird das Programm, das Funktionsaufrufe und Zugriffe zwischen Bibliotheken und Quellen (Übersetzungseinheiten) auflöst, als Linker bezeichnet.

Der Linker funktioniert nicht mit Headern. Es erstellt einfach eine große Tabelle aller Namen, die in allen Übersetzungseinheiten und Bibliotheken definiert sind, und verknüpft diese Namen dann mit den Codezeilen, die auf sie zugreifen. Die archaische Verwendung von C erlaubt sogar den Aufruf einer Funktion ohne Implementierungsdeklaration; es wurde einfach angenommen, dass jeder undefinierte Typ ein ist int.

Im Allgemeinen, wenn Sie eine Datei wie folgt kompilieren:

gcc -o program program.c

Sie rufen wirklich ein Treiberprogramm auf, das Folgendes tut:

  • Vorverarbeitung (wenn Sie darum gebeten haben, dass dies ein separater Schritt ist) mit cpp.
  • Kompilieren (kann in die Vorverarbeitung integriert werden) mit cc1
  • zusammenbauen, verwenden as (gas, der GNU-Assembler).
  • verlinken mit collect2die auch verwendet ld (der GNU-Linker).

Typischerweise erstellen Sie während der ersten 3 Phasen eine einfache Objektdatei (.o Erweiterung), die durch Kompilieren einer Kompilierungseinheit erstellt wird (d. h. eine .c-Datei, bei der die #include- und andere Direktiven durch den Präprozessor ersetzt werden).

Die vierte Phase ist diejenige, die die endgültige ausführbare Datei erstellt. Nach der Kompilierung einer Unit markiert der Compiler mehrere Codeteile als Referenzen, die vom Linker aufgelöst werden müssen. Die Aufgabe des Linkers besteht darin, unter vielen Kompilierungseinheiten zu suchen und Verweise auf externe Kompilierungseinheiten aufzulösen.

1407950cookie-checkC-Header-Dateien und Kompilierung/Linking

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

Privacy policy