Comment fonctionne le processus de compilation / liaison?

417

Comment fonctionne le processus de compilation et de liaison?

(Remarque: Ceci est censé être une entrée de la FAQ C ++ de Stack Overflow . Si vous voulez critiquer l'idée de fournir une FAQ sous cette forme, alors la publication sur la méta qui a commencé tout cela serait l'endroit pour le faire. Réponses à cette question est surveillée dans le salon de discussion C ++ , où l'idée de FAQ a commencé en premier lieu, donc votre réponse est très susceptible d'être lue par ceux qui ont eu l'idée.)

inconnu
la source

Réponses:

555

La compilation d'un programme C ++ comprend trois étapes:

  1. Prétraitement: le préprocesseur prend un fichier de code source C ++ et traite les #includes, #defines et autres directives de préprocesseur. La sortie de cette étape est un fichier C ++ "pur" sans directives de pré-processeur.

  2. Compilation: le compilateur prend la sortie du pré-processeur et en produit un fichier objet.

  3. Liaison: l'éditeur de liens prend les fichiers objets produits par le compilateur et produit soit une bibliothèque soit un fichier exécutable.

Prétraitement

Le préprocesseur gère les directives du préprocesseur , comme #includeet #define. Il est indépendant de la syntaxe du C ++, c'est pourquoi il doit être utilisé avec précaution.

Il fonctionne sur un fichier C ++ source à la fois en remplaçant les #includedirectives avec le contenu des fichiers respectifs ( ce qui est généralement seulement des déclarations), en faisant le remplacement des macros ( #define), et en sélectionnant différentes portions de texte en fonction des #if, #ifdefet des #ifndefdirectives.

Le préprocesseur fonctionne sur un flux de jetons de prétraitement. La substitution de macro est définie comme le remplacement de jetons par d'autres jetons (l'opérateur ##permet de fusionner deux jetons lorsque cela a du sens).

Après tout cela, le préprocesseur produit une sortie unique qui est un flux de jetons résultant des transformations décrites ci-dessus. Il ajoute également des marqueurs spéciaux qui indiquent au compilateur d'où vient chaque ligne afin qu'il puisse les utiliser pour produire des messages d'erreur sensibles.

Certaines erreurs peuvent être produites à ce stade avec une utilisation intelligente des directives #ifet #error.

Compilation

L'étape de compilation est effectuée sur chaque sortie du préprocesseur. Le compilateur analyse le code source C ++ pur (désormais sans directives de préprocesseur) et le convertit en code assembleur. Invoque ensuite le back-end sous-jacent (assembleur dans la chaîne d'outils) qui assemble ce code en code machine produisant un fichier binaire réel dans un certain format (ELF, COFF, a.out, ...). Ce fichier objet contient le code compilé (sous forme binaire) des symboles définis dans l'entrée. Les symboles dans les fichiers objets sont désignés par leur nom.

Les fichiers objets peuvent faire référence à des symboles qui ne sont pas définis. C'est le cas lorsque vous utilisez une déclaration et n'en fournissez pas de définition. Le compilateur ne s'en soucie pas et produira volontiers le fichier objet tant que le code source est bien formé.

Les compilateurs vous permettent généralement d'arrêter la compilation à ce stade. Ceci est très utile car avec lui, vous pouvez compiler chaque fichier de code source séparément. L'avantage que cela offre est que vous n'avez pas besoin de tout recompiler si vous ne modifiez qu'un seul fichier.

Les fichiers objets produits peuvent être placés dans des archives spéciales appelées bibliothèques statiques, pour une réutilisation plus facile plus tard.

C'est à ce stade que les erreurs de compilation "normales", comme les erreurs de syntaxe ou les erreurs de résolution de surcharge échouées, sont signalées.

Mise en relation

L'éditeur de liens est ce qui produit la sortie de compilation finale à partir des fichiers objets produits par le compilateur. Cette sortie peut être soit une bibliothèque partagée (ou dynamique) (et bien que le nom soit similaire, ils n'ont pas grand-chose en commun avec les bibliothèques statiques mentionnées précédemment) ou un exécutable.

Il relie tous les fichiers objets en remplaçant les références aux symboles non définis par les adresses correctes. Chacun de ces symboles peut être défini dans d'autres fichiers objets ou dans des bibliothèques. S'ils sont définis dans des bibliothèques autres que la bibliothèque standard, vous devez en informer l'éditeur de liens.

À ce stade, les erreurs les plus courantes sont les définitions manquantes ou les définitions en double. La première signifie que les définitions n'existent pas (c'est-à-dire qu'elles ne sont pas écrites) ou que les fichiers ou bibliothèques d'objets où elles résident n'ont pas été fournis à l'éditeur de liens. Cette dernière est évidente: le même symbole a été défini dans deux fichiers ou bibliothèques d'objets différents.

R. Martinho Fernandes
la source
39
L'étape de compilation appelle également l'assembleur avant la conversion en fichier objet.
Manav mn
3
Où les optimisations sont-elles appliquées? À première vue, il semble que ce serait fait à l'étape de la compilation, mais d'un autre côté, je peux imaginer qu'une optimisation correcte ne peut être effectuée qu'après la liaison.
Bart van Heukelom
6
@BartvanHeukelom, cela se faisait traditionnellement lors de la compilation, mais les compilateurs modernes prennent en charge la soi-disant "optimisation du temps de liaison" qui a l'avantage de pouvoir optimiser entre les unités de traduction.
R. Martinho Fernandes
3
C a-t-il les mêmes étapes?
Kevin Zhu
6
Si l'éditeur de liens convertit les symboles faisant référence aux classes / méthodes des bibliothèques en adresses, cela signifie-t-il que les fichiers binaires de la bibliothèque sont stockés dans des adresses mémoire que le système d'exploitation garde constantes? Je suis juste confus quant à la façon dont l'éditeur de liens connaîtrait l'adresse exacte, par exemple, du binaire stdio pour tous les systèmes cibles. Le chemin du fichier serait toujours le même, mais l'adresse exacte peut changer, non?
Dan Carter
42

Ce sujet est abordé sur CProgramming.com:
https://www.cprogramming.com/compilingandlinking.html

Voici ce que l'auteur y a écrit:

La compilation n'est pas tout à fait la même chose que la création d'un fichier exécutable! Au lieu de cela, la création d'un exécutable est un processus à plusieurs étapes divisé en deux composants: la compilation et la liaison. En réalité, même si un programme "compile bien", il pourrait ne pas fonctionner en raison d'erreurs pendant la phase de liaison. Le processus total de passage des fichiers de code source à un exécutable pourrait être mieux appelé une construction.

Compilation

La compilation fait référence au traitement des fichiers de code source (.c, .cc ou .cpp) et à la création d'un fichier «objet». Cette étape ne crée rien que l'utilisateur puisse réellement exécuter. Au lieu de cela, le compilateur produit simplement les instructions du langage machine qui correspondent au fichier de code source qui a été compilé. Par exemple, si vous compilez (mais ne liez pas) trois fichiers distincts, vous aurez trois fichiers objets créés en sortie, chacun avec le nom .o ou .obj (l'extension dépendra de votre compilateur). Chacun de ces fichiers contient une traduction de votre fichier de code source dans un fichier en langage machine - mais vous ne pouvez pas encore les exécuter! Vous devez les transformer en exécutables que votre système d'exploitation peut utiliser. C'est là que l'éditeur de liens entre en jeu.

Mise en relation

La liaison fait référence à la création d'un seul fichier exécutable à partir de plusieurs fichiers objet. Dans cette étape, il est courant que l'éditeur de liens se plaigne des fonctions non définies (généralement, principal lui-même). Pendant la compilation, si le compilateur ne pouvait pas trouver la définition d'une fonction particulière, il supposerait simplement que la fonction était définie dans un autre fichier. Si ce n'est pas le cas, le compilateur n'a aucun moyen de le savoir - il ne regarde pas le contenu de plus d'un fichier à la fois. L'éditeur de liens, d'autre part, peut regarder plusieurs fichiers et essayer de trouver des références pour les fonctions qui n'ont pas été mentionnées.

Vous pourriez vous demander pourquoi il existe des étapes de compilation et de liaison distinctes. Tout d'abord, il est probablement plus facile d'implémenter les choses de cette façon. Le compilateur fait sa chose, et l'éditeur de liens fait sa chose - en gardant les fonctions séparées, la complexité du programme est réduite. Un autre avantage (plus évident) est qu'il permet la création de gros programmes sans avoir à refaire l'étape de compilation à chaque fois qu'un fichier est modifié. Au lieu de cela, en utilisant ce qu'on appelle la "compilation conditionnelle", il est nécessaire de compiler uniquement les fichiers source qui ont changé; pour le reste, les fichiers objets sont une entrée suffisante pour l'éditeur de liens. Enfin, cela facilite l'implémentation de bibliothèques de code précompilé: il suffit de créer des fichiers objets et de les lier comme n'importe quel autre fichier objet.

Pour tirer pleinement parti de la compilation des conditions, il est probablement plus facile d'obtenir un programme pour vous aider que d'essayer de vous souvenir des fichiers que vous avez modifiés depuis la dernière compilation. (Vous pouvez, bien sûr, simplement recompiler chaque fichier dont l'horodatage est supérieur à l'horodatage du fichier objet correspondant.) Si vous travaillez avec un environnement de développement intégré (IDE), il se peut que cela s'occupe déjà de vous. Si vous utilisez des outils de ligne de commande, il existe un utilitaire astucieux appelé make qui vient avec la plupart des distributions * nix. En plus de la compilation conditionnelle, il a plusieurs autres fonctionnalités intéressantes pour la programmation, telles que permettre différentes compilations de votre programme - par exemple, si vous avez une version produisant une sortie détaillée pour le débogage.

Connaître la différence entre la phase de compilation et la phase de liaison peut faciliter la recherche de bogues. Les erreurs de compilation sont généralement de nature syntaxique - un point-virgule manquant, une parenthèse supplémentaire. Les erreurs de liaison sont généralement liées à des définitions manquantes ou multiples. Si vous obtenez une erreur indiquant qu'une fonction ou une variable est définie plusieurs fois à partir de l'éditeur de liens, c'est une bonne indication que l'erreur est que deux de vos fichiers de code source ont la même fonction ou variable.

neuronet
la source
1
Ce que je ne comprends pas, c'est que si le préprocesseur gère des choses comme #includes pour créer un super fichier, alors il n'y a sûrement rien à lier après cela?
binarysmacker
@binarysmacer Voyez si ce que j'ai écrit ci-dessous vous semble logique. J'ai essayé de décrire le problème de l'intérieur.
Vue elliptique du
3
@binarysmacker Il est trop tard pour commenter cela, mais d'autres pourraient trouver cela utile. youtu.be/D0TazQIkc8Q Fondamentalement, vous incluez des fichiers d'en-tête et ces fichiers d'en-tête ne contiennent généralement que les déclarations de variables / fonctions et pas de définitions, les définitions peuvent être présentes dans un fichier source séparé. l'éditeur de liens vous aide à lier le fichier source qui utilise la variable / fonction au fichier source qui les définit.
Karan Joisher
24

Sur le front standard:

  • une unité de traduction est la combinaison d'un fichier source, d'en-têtes inclus et de fichiers source moins toutes les lignes source ignorées par la directive de préprocesseur d'inclusion conditionnelle.

  • la norme définit 9 phases dans la traduction. Les quatre premiers correspondent au prétraitement, les trois suivants sont la compilation, le suivant est l'instanciation des modèles (produisant des unités d'instanciation ) et le dernier est la liaison.

Dans la pratique, la huitième phase (l'instanciation des modèles) se fait souvent pendant le processus de compilation, mais certains compilateurs la retardent à la phase de liaison et d'autres la répartissent dans les deux.

AProgrammer
la source
14
Pourriez-vous énumérer les 9 phases? Ce serait un bon ajout à la réponse, je pense. :)
jalf
@jalf, ajoutez simplement l'instanciation du modèle juste avant la dernière phase de la réponse pointée par @sbi. IIRC il y a des différences subtiles dans la formulation précise dans la gestion des caractères larges, mais je ne pense pas qu'ils apparaissent dans les étiquettes des diagrammes.
AProgrammer
2
@sbi oui, mais c'est censé être la question FAQ, n'est-ce pas? Ces informations ne devraient-elles pas être disponibles ici ? ;)
jalf
3
@AProgrammmer: simplement les énumérer par nom serait utile. Ensuite, les gens savent quoi rechercher s'ils veulent plus de détails. Quoi qu'il en soit, + 1'ed votre réponse dans tous les cas :)
jalf
14

Le maigre est qu'un CPU charge des données à partir d'adresses de mémoire, stocke des données dans des adresses de mémoire et exécute des instructions séquentiellement à partir d'adresses de mémoire, avec quelques sauts conditionnels dans la séquence d'instructions traitées. Chacune de ces trois catégories d'instructions implique le calcul d'une adresse vers une cellule mémoire à utiliser dans l'instruction machine. Parce que les instructions machine sont d'une longueur variable en fonction de l'instruction particulière impliquée, et parce que nous enchaînons une longueur variable ensemble pendant que nous construisons notre code machine, il y a un processus en deux étapes impliqué dans le calcul et la construction des adresses.

Nous établissons d'abord l'allocation de la mémoire du mieux que nous pouvons avant de savoir ce qui se passe exactement dans chaque cellule. Nous déterminons les octets, ou les mots, ou quoi que ce soit qui forment les instructions et les littéraux et toutes les données. Nous commençons simplement à allouer de la mémoire et à construire les valeurs qui créeront le programme au fur et à mesure, et notons tout endroit où nous devons revenir en arrière et fixer une adresse. À cet endroit, nous mettons un mannequin pour simplement remplir l'emplacement afin que nous puissions continuer à calculer la taille de la mémoire. Par exemple, notre premier code machine peut prendre une cellule. Le code machine suivant peut prendre 3 cellules, impliquant une cellule de code machine et deux cellules d'adresse. Maintenant, notre pointeur d'adresse est 4. Nous savons ce qui se passe dans la cellule machine, qui est le code op, mais nous devons attendre de calculer ce qui se passe dans les cellules d'adresse jusqu'à ce que nous sachions où ces données seront situées, c'est-à-dire

S'il n'y avait qu'un seul fichier source, un compilateur pourrait théoriquement produire du code machine entièrement exécutable sans éditeur de liens. Dans un processus en deux étapes, il pourrait calculer toutes les adresses réelles de toutes les cellules de données référencées par n'importe quelle instruction de chargement ou de stockage de la machine. Et il pourrait calculer toutes les adresses absolues référencées par toutes les instructions de saut absolu. C'est ainsi que les compilateurs plus simples, comme celui de Forth fonctionnent, sans éditeur de liens.

Un éditeur de liens est quelque chose qui permet de compiler des blocs de code séparément. Cela peut accélérer le processus global de construction du code et permet une certaine flexibilité dans la façon dont les blocs sont utilisés plus tard, en d'autres termes, ils peuvent être déplacés en mémoire, par exemple en ajoutant 1000 à chaque adresse pour analyser le bloc par 1000 cellules d'adresse.

Donc, ce que le compilateur produit est un code machine approximatif qui n'est pas encore entièrement construit, mais qui est disposé de manière à ce que nous connaissions la taille de tout, en d'autres termes afin que nous puissions commencer à calculer où toutes les adresses absolues seront situées. le compilateur génère également une liste de symboles qui sont des paires nom / adresse. Les symboles correspondent à un décalage de mémoire dans le code machine du module avec un nom. Le décalage étant la distance absolue à l'emplacement mémoire du symbole dans le module.

C'est là que nous arrivons à l'éditeur de liens. L'éditeur de liens claque d'abord tous ces blocs de code machine de bout en bout et note où chacun commence. Il calcule ensuite les adresses à fixer en additionnant le décalage relatif dans un module et la position absolue du module dans la plus grande disposition.

Évidemment, j'ai simplifié à l'excès afin que vous puissiez essayer de le comprendre, et je n'ai délibérément pas utilisé le jargon des fichiers objets, des tables de symboles, etc. qui, pour moi, fait partie de la confusion.

mon nom d'utilisateur a été détourné ici
la source
13

GCC compile un programme C / C ++ en exécutable en 4 étapes.

Par exemple, gcc -o hello hello.cest effectuée comme suit:

1. Prétraitement

Prétraitement via le préprocesseur GNU C ( cpp.exe), qui inclut les en-têtes ( #include) et étend les macros ( #define).

cpp hello.c > hello.i

Le fichier intermédiaire résultant "hello.i" contient le code source développé.

2. Compilation

Le compilateur compile le code source prétraité en code assembleur pour un processeur spécifique.

gcc -S hello.i

L'option -S spécifie de produire du code assembleur, au lieu du code objet. Le fichier d'assemblage résultant est "hello.s".

3. Assemblage

L'assembleur ( as.exe) convertit le code assembleur en code machine dans le fichier objet "hello.o".

as -o hello.o hello.s

4. Linker

Enfin, l'éditeur de liens ( ld.exe) relie le code objet au code bibliothèque pour produire un fichier exécutable "bonjour".

    ld -o hello hello.o ... bibliothèques ...
kaps
la source
9

Regardez l'URL: http://faculty.cs.niu.edu/~mcmahon/CS241/Notes/compile.html
Le processus compling complet de C ++ est introduit clairement dans cette URL.

Charles Wang
la source
2
Merci d'avoir partagé cela, c'est tellement simple et direct à comprendre.
Mark
Bon, ressource, pouvez-vous mettre une explication de base du processus ici, la réponse est signalée par l'algorithme comme b / c de faible qualité, c'est court et juste l'url.
JasonB
Un joli petit tutoriel que j'ai trouvé: calleerlandsson.com/the-four-stages-of-compiling-ac-program
Guy Avraham