Comment fonctionne le processus de compilation/liaison?
Comment fonctionne le processus de compilation et de liaison?
(Note: Ceci est censé être une entrée à Stack Overflow C++ FAQ . Si vous voulez critiquer l'idée de fournir une FAQ sous cette forme, alors l'affichage sur meta qui a commencé tout cela serait l'endroit pour le faire. Les réponses à cette question sont suivies dans le c++ chatroom , 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.)
6 réponses
la compilation d'un programme C++ comporte trois étapes:
-
prétraitement: le préprocesseur prend un fichier de code source C++ et traite des directives
#include
s,#define
s et autres directives de préprocesseur. La sortie de cette étape est un fichier C++" PUR " sans directives pré-processeur. -
Compilation: le compilateur prend la sortie du pré-processeur et produit un fichier objet à partir d'elle.
-
lien: le linker prend les fichiers objets produits par le compilateur et produit soit une bibliothèque ou un fichier exécutable.
prétraitement
le préprocesseur manipule les directives préprocesseur , comme #include
et #define
. Il est agnostique de la syntaxe de C++, qui est pourquoi il doit être utilisé avec précaution.
il fonctionne sur un fichier source C++ à la fois en remplaçant les directives #include
par le contenu des fichiers respectifs (qui sont généralement de simples déclarations), en remplaçant les macros ( #define
), et en sélectionnant différentes parties de texte en fonction des directives #if
, #ifdef
et #ifndef
.
le préprocesseur travaille sur un flux de jetons de prétraitement. La substitution Macro est définie comme le remplacement des tokens par d'autres tokens (les l'opérateur ##
permet de fusionner deux tokens quand 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ù provient chaque ligne afin qu'il puisse les utiliser pour produire des messages d'erreur sensibles.
quelques erreurs peuvent être produites à ce stade avec l'utilisation intelligente du #if
et "1519110920 des" directives".
Compilation
L'étape de compilation est effectuée sur chaque sortie du préprocesseur. Le compilateur analyse le code source C++ pur (maintenant sans aucune directive préprocesseur) et le convertit en code d'assemblage. Puis invoque arrière-fond sous-jacent (assembleur dans chaîne d'outils) qui assemble ce code en code machine produisant le fichier binaire réel dans un certain format(ELF, COFF, A. dehors ...). Ce fichier objet contient le fichier compilé code (sous forme binaire) des symboles définis dans l'entrée. Les symboles dans les fichiers d'objets sont désignés par leur nom.
les fichiers objets peuvent renvoyer à des symboles qui ne sont pas définis. C'est le cas lorsque vous utilisez une déclaration, et ne fournissent pas de définition. Le compilateur s'en fiche et produira volontiers le fichier objet tant que le code source est bien formé.
compilateurs habituellement vous permettent d'arrêter la compilation à ce point. C'est très utile parce qu'avec lui vous pouvez compiler chaque fichier de code source séparément. L'avantage est que vous n'avez pas besoin de recompiler tout si vous ne changez qu'un seul fichier.
les fichiers objets produits peuvent être placés dans des archives spéciales appelées bibliothèques statiques, pour faciliter leur réutilisation ultérieure.
c'est à ce stade que les erreurs "régulières" du compilateur, comme les erreurs de syntaxe ou les erreurs de résolution de surcharge, sont signaler.
Linking
le linker est ce qui produit la sortie de compilation finale à partir des fichiers objets que le compilateur produit. 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.
relie tous les fichiers d'objets en remplaçant les références aux symboles non définis par les bonnes adresses. Chacun de ces symboles peuvent être définis dans d'autres fichiers de l'objet ou dans des bibliothèques. Si elles sont définies dans des bibliothèques autres que la bibliothèque standard, vous devez en parler au linker.
à ce stade, les erreurs les plus courantes sont les définitions manquantes ou les définitions en double. La première signifie que soit les définitions n'existent pas (c'est-à-dire qu'elles ne sont pas écrites), soit les fichiers objets ou les bibliothèques où ils résident n'ont pas été donnés au linker. Ce dernier est évident: le même le symbole a été défini dans deux fichiers ou bibliothèques d'objets différents.
ce sujet est discuté à CProgramming.com:
https://www.cprogramming.com/compilingandlinking.html
voici ce que l'auteur y a écrit:
compiler n'est pas tout à fait la même chose que créer un fichier exécutable! Au lieu de cela, la création d'un exécutable est un processus en plusieurs étapes divisé en deux composantes: compilation et mise en relation. En réalité, même si un programme "compile bien" il pourrait ne pas fonctionner réellement en raison d'erreurs le lien de phase. Le processus total d'aller à partir de fichiers de code source à un exécutable pourrait mieux être appelé une construction.
Compilation
Compilation se réfère à la transformation de fichiers de code source (.c,.cc, ou .rpc) et la création d'un "objet" de fichier. Cette étape ne crée pas tout ce que l'utilisateur peut s'exécuter. Au lieu de cela, le compilateur simplement produit le langage machine les instructions qui correspondent au fichier de code source compilé. Par exemple, si vous compilez (mais ne pas lier) trois fichiers séparés, vous aurez trois fichiers d'objet créé en sortie, chacune 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 une machine fichier de langue -- mais vous ne pouvez pas les Exécuter encore! Vous avez besoin de les activer dans des exécutables de votre système d'exploitation peut utiliser. C'est là que le l'éditeur de liens.
Linking
lien se réfère à la création d'un seul fichier exécutable à partir de plusieurs fichiers d'objets. Dans cette étape, il est fréquent que l'éditeur de liens se plaindre de fonctions non définies (généralement, se main). Lors compilation, si le compilateur ne pouvait pas trouver la définition d'un fonction particulière, il serait juste supposer que la fonction était définie dans un autre fichier. Si ce n'est pas le cas, il n'y a aucun moyen le compilateur saurait -- il ne regarde pas le contenu de plus de un seul fichier à la fois. L'éditeur de liens, d'autre part, peuvent regarder plusieurs fichiers et essayer de trouver des références pour les fonctions qui n'ont pas été mentionnés.
vous pourriez vous demander pourquoi il y a des étapes distinctes de compilation et de lien. D'abord, il est probablement plus facile de mettre en œuvre les choses de cette façon. Compilateur fait sa chose, et l'éditeur de liens ne son truc, en gardant la fonctions distinctes, le la complexité du programme est réduite. Un autre (plus évident) l'avantage est que cela permet la création d'un grand programmes sans avoir à refaire l'étape de compilation à chaque fois un fichier est changé. Au lieu de cela, en utilisant la soi-disant "compilation conditionnelle", il est nécessaire pour compiler uniquement les fichiers source qui ont changé le reste, les fichiers objets sont suffisants pour le linker. Enfin, cela simplifie l'implémentation des bibliothèques de pré-compilées code: il suffit de créer l'objet les fichiers et les lier comme les autres fichier de l'objet. (Le fait que chaque fichier est compilé séparément l'information contenue dans d'autres dossiers, soit dit en passant, est appelée "séparer le modèle de compilation".)
pour obtenir tous les avantages de la compilation de condition, il est probablement plus facile d'obtenir un programme pour vous aider que d'essayer de se rappeler qui fichiers que vous avez modifiés depuis votre dernière compilation. (Vous pouvez, bien sûr, il suffit de recompiler tous les fichiers qui ont une horodatage de plus que le horodatage du fichier objet correspondant.) Si vous travaillez avec un environnement de développement intégré (IDE), il peut déjà prendre soin de cela pour vous. Si vous utilisez des outils en ligne de commande, il y a une bonne utilitaire appelé make qui est livré avec la plupart des distributions *nix. Ainsi avec la compilation conditionnelle, il a plusieurs autres fonctionnalités intéressantes pour programmation, comme permettre différentes compilations de votre programme -- par exemple, si vous avez une version de production détaillé de sortie pour le débogage.
connaître la différence entre la phase de compilation et le lien la phase peut faciliter la chasse aux 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 ont généralement à voir avec manquant ou plusieurs définition. Si vous obtenez une erreur qu'une fonction ou une variable est défini plusieurs fois à partir du linker, c'est une bonne indication que l'erreur est que deux de vos sources les fichiers de code ont la même fonction ou variable.
sur le front standard:
-
a translation unit est la combinaison d'un fichier source, d'en-têtes inclus et de fichiers source moins toutes les lignes source sautées par la directive 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 le instantiation de gabarits (production instantiation units ) et le dernier est le lien.
dans la pratique, la huitième phase (l'instanciation de gabarits) est souvent effectuée au cours du processus de compilation, mais certains compilateurs retardent le processus à la phase de liaison et d'autres l'étalent dans les deux.
le mince est qu'un CPU charge des données à partir des adresses mémoire, stocke des données aux adresses mémoire, et exécute des instructions séquentiellement hors des adresses 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 à 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 en cause, et parce que nous la chaîne d'une longueur variable d'entre eux ensemble pendant que nous construisons notre Code machine, il y a un processus en deux étapes impliquées dans le calcul et la construction des adresses.
tout d'abord, nous établissons la répartition de la mémoire du mieux que nous pouvons avant que nous puissions savoir ce qui se passe exactement dans chaque cellule. Nous trouvons les bytes, ou les mots, ou quoi que ce soit qui forme les instructions et les littérales et toutes les données. Nous commençons juste à allouer la mémoire et à construire les valeurs qui créeront le programme au fur et à mesure, et notez où qu'on aille, on doit retourner réparer une adresse. À cet endroit, nous avons mis un mannequin pour simplement pad l'emplacement afin que nous puissions continuer à calculer la taille de la mémoire. Par exemple, notre premier code machine pourrait prendre une cellule. Le prochain code machine pourrait 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 va dans la cellule machine, qui est le code op, mais nous devons attendre de calculer ce qui va dans les cellules d'adresse jusqu'à ce que nous savons où ces données seront être localisés, c'est-à-dire quelle sera l'adresse machine de ces données.
S'il n'y avait qu'un seul fichier source, un compilateur pourrait théoriquement produire du code machine entièrement exécutable sans linker. Dans un processus de deux passes il pourrait calculer toutes les adresses réelles à toutes les cellules de données référencées par n'importe quelle charge de la machine ou des instructions de stockage. Et il pourrait calculer toutes les adresses absolues référencées par n'importe quelle instruction de saut absolu. C'est ainsi que des compilateurs plus simples, comme celui qui est au travail, sans linker.
un linker est quelque chose qui permet aux blocs de code d'être compilés séparément. Cela peut accélérer le processus global de code de construction, et permet une certaine flexibilité avec 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 scoot le bloc par 1000 cellules d'adresse.
donc ce que les sorties du compilateur est un code machine approximatif qui n'est pas encore complètement construit, mais est disposé de sorte que nous connaissons la taille de tout, en d'autres termes, nous pouvons commencer à calculer où toutes les adresses absolues seront situés. le compilateur affiche également une liste de symboles qui sont des paires nom/adresse. Les symboles rapportent un décalage de mémoire dans le code machine dans le module avec un nom. Le décalage étant la distance absolue à la position mémoire du symbole dans le module.
c'est là que nous arrivons à la ligne. L'éditeur de liens de la première gifle tous de ces blocs de code machine ensemble bout à bout et note En bas où chacun commence. Il calcule ensuite les adresses à corriger en additionnant l'offset relatif à l'intérieur d'un module et la position absolue du module dans la plus grande disposition.
évidemment j'ai trop simplifié cela pour que vous puissiez essayer de le saisir, et je n'ai délibérément pas utilisé le jargon des fichiers d'objets, des tables de symboles, etc. ce qui pour moi est une partie de la confusion.
regardez L'URL: http://faculty.cs.niu.edu/~mcmahon/CS241/Notes/compile.html
Le processus complet de complication de C++ est présenté clairement dans cette URL.
GCC compile un programme C/C++ en exécutable en 4 étapes.
par exemple, un " gcc -o hello.exe hello.c
" est effectuée comme suit:
1. Prétraitement
Préprocessin via le préprocesseur GNU C (cpp.exe), qui comprend les en-têtes (#include) et étend les macros (#define).
rpc bonjour.c > bonjour.je
le fichier intermédiaire résultant" bonjour.I " contient le code source étendu.
2. Compilation
le compilateur compile le code source pré-traité en code d'assemblage pour un processeur spécifique.
gcc-s hello.i
l'option-S spécifie de produire l'assemblage code, au lieu du code objet. Le fichier d'assemblage qui en résulte est " hello.s".
3. Assemblage
l'assembleur (as.exe) convertit le code d'assemblage en code machine dans le fichier objet "hello.o".
as-o bonjour.o bonjour.s
4. L'éditeur de liens
enfin, le linker (ld.exe) liens le code objet avec le code de la bibliothèque pour produire un fichier exécutable "bonjour.EXE."
ld -o bonjour.exe bonjour.o ...bibliothèque...