web-dev-qa-db-fra.com

Fichiers d'en-tête C et compilation / liaison

Je sais que les fichiers d'en-tête ont des déclarations avancées de diverses fonctions, structures, etc. qui sont utilisées dans le fichier .c Qui "appelle" le #include, N'est-ce pas? Autant que je sache, la "séparation des pouvoirs" se produit comme ceci:

Fichier d'en-tête: func.h

  • contient une déclaration de fonction avant

    int func(int i);
    

Fichier source C: func.c

  • contient la définition réelle de la fonction

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

Fichier source C source.c (Le programme "réel"):

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

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

Ma question est: voyant que le #include Est simplement une directive du compilateur qui copie le contenu du .h Dans le fichier où se trouve #include, Comment fonctionne le .c savoir comment exécuter réellement la fonction? Tout ce qu'il obtient, c'est la int func(int i);, alors comment peut-elle réellement exécuter la fonction? Comment accède-t-il à la définition réelle de func? L'en-tête comprend-il une sorte de "pointeur" qui dit "c'est ma définition, là-bas!"?

Comment ça marche?

27
Aristides

Uchia Itachi a donné la réponse. C'est le linker.

En utilisant GNU C compiler gcc vous compileriez un programme à un fichier comme

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

Mais en compilant le programme à deux fichiers (ou plus) comme décrit dans votre exemple, vous devez effectuer les opérations suivantes:

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

Chaque fichier objet a des symboles externes (vous pouvez le considérer comme des membres publics). Les fonctions sont par défaut externes tandis que les variables (globales) sont par défaut internes. Vous pouvez modifier ce comportement en définissant

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

ou

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

Lorsque vous rencontrez un appel à une fonction, non définie dans le module principal, l'éditeur de liens recherche tous les fichiers objets (et bibliothèques) fournis en entrée pour le module où la fonction appelée est définie. Par défaut, vous avez probablement des bibliothèques liées à votre programme, c'est ainsi que vous pouvez utiliser printf, il est déjà compilé dans une bibliothèque.

Si vous êtes vraiment intéressé, essayez une programmation d'assemblage. Ces noms sont l'équivalent d'étiquettes dans le code d'assemblage.

28
Emil Vatai

C'est l'éditeur de liens qui gère tout cela. Le compilateur émet simplement une séquence spéciale dans le fichier objet disant "J'ai ce symbole externe func, veuillez le résoudre" pour l'éditeur de liens. L'éditeur de liens le voit et recherche tous les autres fichiers et bibliothèques d'objets pour le symbole.

14

Une déclaration d'un symbole sans définition dans la même unité de compilation indique au compilateur de compiler avec un espace réservé pour l'adresse de ce symbole dans un fichier objet.

L'éditeur de liens verra qu'une définition du symbole est requise et recherchera des définitions externes du symbole dans les bibliothèques et autres fichiers objets.

Si l'éditeur de liens trouve une définition, l'espace réservé dans le fichier objet d'origine sera remplacé par l'adresse trouvée dans l'exécutable final.

3
Henrik

L'en-tête donne accès non seulement à d'autres .c fichiers dans le même programme, mais également aux bibliothèques qui peuvent être distribuées sous forme binaire. La relation d'un .c fichier vers un autre est exactement la même chose qu'une bibliothèque qui en dépend.

Puisqu'une interface de programmation doit être sous forme de texte quel que soit le format de l'implémentation, les fichiers d'en-tête ont un sens en tant que séparation des préoccupations.

Comme d'autres l'ont mentionné, le programme qui résout les appels de fonction et les accès entre les bibliothèques et les sources (unités de traduction) est appelé l'éditeur de liens.

L'éditeur de liens ne fonctionne pas avec les en-têtes. Il crée simplement un grand tableau de tous les noms définis dans toutes les unités de traduction et bibliothèques, puis relie ces noms aux lignes de code qui y accèdent. L'utilisation archaïque de C permet même d'appeler une fonction sans aucune déclaration d'implémentation; on supposait simplement que chaque type non défini était un int.

2
Potatoswatter

Généralement, lorsque vous compilez un fichier comme celui-ci:

gcc -o program program.c

Vous appelez vraiment un programme pilote, qui fait ce qui suit:

  • prétraitement (si vous avez demandé que ce soit une étape distincte) en utilisant cpp.
  • compilation (peut être intégrée au prétraitement) en utilisant cc1
  • assemblage, en utilisant as (gas, l'assembleur GNU).
  • liaison à l'aide de collect2, qui utilise également ld (l'éditeur de liens GNU)).

En règle générale, au cours des 3 premières étapes, vous créez un fichier objet simple (.o extension), qui est créée en compilant une unité de compilation (c'est-à-dire un fichier .c, avec les directives #include et autres remplacées par le préprocesseur).

La 4ème étape est celle qui crée l'exécutable final. Après la compilation d'une unité, le compilateur marque plusieurs morceaux de code en tant que références qui doivent être résolues par l'éditeur de liens. Le travail de l'éditeur de liens consiste à rechercher parmi de nombreuses unités de compilation et à résoudre les références aux unités de compilation externes.

2
NlightNFotis