La compilation d'un programme C ++ comprend trois étapes:
Prétraitement: le préprocesseur prend un fichier de code source C ++ et traite les #include
s, #define
s et autres directives de préprocesseur. La sortie de cette étape est un fichier C ++ "pur" sans directives de pré-processeur.
Compilation: le compilateur prend la sortie du pré-processeur et en produit un fichier objet.
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 #include
et #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 #include
directives 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
, #ifdef
et des #ifndef
directives.
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 #if
et #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.
Ce sujet est abordé sur CProgramming.com:
https://www.cprogramming.com/compilingandlinking.html
Voici ce que l'auteur y a écrit:
la source
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.
la source
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.
la source
GCC compile un programme C / C ++ en exécutable en 4 étapes.
Par exemple,
gcc -o hello hello.c
est 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
).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.
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".4. Linker
Enfin, l'éditeur de liens (
ld.exe
) relie le code objet au code bibliothèque pour produire un fichier exécutable "bonjour".la source
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.
la source