Un compilateur est un programme qui traduit votre code source en un fichier contenant des instructions exécutables par le système d'exploitation de votre ordinateur. La compilation est un processus complexe. En sciences de l'informatique, elle constitue un domaine de recherche à part entière.
clang/clang++/(flang)
¶clang
)Soit un fichier minimal helloworld.cpp
contenant :
pygmentize -g helloworld.cpp
#include <iostream> using namespace std; int main() { // affiche Hello World ! cout << "Hello World !" << endl; return 0; }
Une compilation basique se fait en une ligne de commande pour produire l'exécutable a.out
g++ helloworld.cpp
./a.out
Hello World !
En réalité, cet appel au compilateur cache plusieurs étapes :
La compilation à proprement parler qui comprend :
source .notebooksrc
Le préprocesseur parcourt l'ensemble du code source que vous avez écrit et interprète les directives de précompilation commençant par #
telle que :
# include <math.h>
On peut demander au compilateur d'afficher le code ainsi traité en utilisant l'option -E
:
g++ -E helloworld.cpp > helloworld.pp
Editer le fichier helloworld.pp pour constater la quantité de code additionnel introduite par le préprocesseur.
head helloworld.pp
printf ".\n.\n.\n"
tail helloworld.pp | pygmentize -g
# 1 "helloworld.cpp" # 1 "<built-in>" # 1 "<command-line>" # 1 "/usr/include/stdc-predef.h" 1 3 4 # 1 "<command-line>" 2 # 1 "helloworld.cpp" # 1 "/usr/include/c++/9/iostream" 1 3 # 36 "/usr/include/c++/9/iostream" 3 # 37 "/usr/include/c++/9/iostream" 3 . . . # 3 "helloworld.cpp" using namespace std; int main() { cout << "Hello World !" << endl; return 0; }
On reconnaît notre code source à la fin (sans les commentaires), précédé par un très grand nombre de lignes.
En réalité, c'est la simple directive
#include <iostream>
qui provoque l'inclusion du fichier header iostream
qui lui-même inclut d'autres headers (ios
, streambuf
, etc.), eux-même incluant d'autres headers, etc. De sorte que le fichier final fait :
echo $(wc -l helloworld.pp | awk '{print $1}') lignes !
28632 lignes !
Rappel : en C++, le préprocesseur est utilisé en particulier pour éviter l'inclusion multiple de fichiers d'en-tête.
pygmentize -g addition/addition.hpp
#ifndef ADDITION_HPP #define ADDITION_HPP int addition(int n, int m); #endif
pygmentize -g addition/main.cpp
#include "addition.hpp" int main(){ return addition(1, 2); }
En effet, sans la directive #ifndef
, le fichier addition.hpp
peut être inclus dans un autre fichier source, ce qui peut provoquer une erreur de compilation.
Elle partitionne le code issu du préprocesseur en jetons lexicaux. Peut souvent se formaliser par une grammaire d'expressions régulières à l'aide d'un automate fini.
À partir de la liste de jetons lexicaux, elle reconnait la structure syntaxique du code à partir de la description grammaticale du langage pour constuire l'arbre syntaxique (AST, pour abstract syntaxic tree) dont :
Exemple avec le programme minimal add.cpp
:
pygmentize -g add.cpp
int addition(int m, int n); int addition(int m, int n){ return m + n; } int main(){ return addition(1, 2); }
On affiche l'AST correspondant en utilisant l'option -ast-dump
du compilateur clang
:
clang -Xclang -ast-dump -fsyntax-only add.cpp
TranslationUnitDecl 0x1380fa8 <<invalid sloc>> <invalid sloc> |-TypedefDecl 0x1381880 <<invalid sloc>> <invalid sloc> implicit __int128_t '__int128' | `-BuiltinType 0x1381540 '__int128' |-TypedefDecl 0x13818f0 <<invalid sloc>> <invalid sloc> implicit __uint128_t 'unsigned __int128' | `-BuiltinType 0x1381560 'unsigned __int128' |-TypedefDecl 0x1381c68 <<invalid sloc>> <invalid sloc> implicit __NSConstantString '__NSConstantString_tag' | `-RecordType 0x13819e0 '__NSConstantString_tag' | `-CXXRecord 0x1381948 '__NSConstantString_tag' |-TypedefDecl 0x1381d00 <<invalid sloc>> <invalid sloc> implicit __builtin_ms_va_list 'char *' | `-PointerType 0x1381cc0 'char *' | `-BuiltinType 0x1381040 'char' |-TypedefDecl 0x13bed58 <<invalid sloc>> <invalid sloc> implicit __builtin_va_list '__va_list_tag [1]' | `-ConstantArrayType 0x13bed00 '__va_list_tag [1]' 1 | `-RecordType 0x1381df0 '__va_list_tag' | `-CXXRecord 0x1381d58 '__va_list_tag' |-FunctionDecl 0x13bef20 <add.cpp:1:1, col:26> col:5 used addition 'int (int, int)' | |-ParmVarDecl 0x13bedc8 <col:14, col:18> col:18 m 'int' | `-ParmVarDecl 0x13bee48 <col:21, col:25> col:25 n 'int' |-FunctionDecl 0x13bf150 prev 0x13bef20 <line:2:1, line:4:1> line:2:5 used addition 'int (int, int)' | |-ParmVarDecl 0x13bf030 <col:14, col:18> col:18 used m 'int' | |-ParmVarDecl 0x13bf0b0 <col:21, col:25> col:25 used n 'int' | `-CompoundStmt 0x13bf2a0 <col:27, line:4:1> | `-ReturnStmt 0x13bf290 <line:3:3, col:14> | `-BinaryOperator 0x13bf270 <col:10, col:14> 'int' '+' | |-ImplicitCastExpr 0x13bf240 <col:10> 'int' <LValueToRValue> | | `-DeclRefExpr 0x13bf200 <col:10> 'int' lvalue ParmVar 0x13bf030 'm' 'int' | `-ImplicitCastExpr 0x13bf258 <col:14> 'int' <LValueToRValue> | `-DeclRefExpr 0x13bf220 <col:14> 'int' lvalue ParmVar 0x13bf0b0 'n' 'int' `-FunctionDecl 0x13bf310 <line:5:1, line:7:1> line:5:5 main 'int ()' `-CompoundStmt 0x13bf500 <col:11, line:7:1> `-ReturnStmt 0x13bf4f0 <line:6:3, col:23> `-CallExpr 0x13bf4c0 <col:10, col:23> 'int' |-ImplicitCastExpr 0x13bf4a8 <col:10> 'int (*)(int, int)' <FunctionToPointerDecay> | `-DeclRefExpr 0x13bf460 <col:10> 'int (int, int)' lvalue Function 0x13bf150 'addition' 'int (int, int)' |-IntegerLiteral 0x13bf420 <col:19> 'int' 1 `-IntegerLiteral 0x13bf440 <col:22> 'int' 2
On distingue les deux fonctions main
et addition
qui sont des noeuds de l'arbre.
Aux extrémités des branches correspondantes, les feuilles sont les variables m
et n
et les constantes 1
et 2
.
L'AST est la représentation intermédiaire interne du programme est utilisée lors de la phase d'optimisation et de génération de code assembleur.
À partir de l'arbre syntaxique, le compilateur crée du code intermédiaire qui va être :
Un exemple inspiré de An Introduction to GCC (Brian Gough) pour illustrer l'effet du niveau d'optimisation :
pygmentize -g sum_power.cpp
#include <iostream> double powern(double d, unsigned n) { double x = 1.0; unsigned j; for (j = 1; j <= n; j++) x *= d; return x; } int main() { double sum = 0.0; unsigned i; for (i = 1; i <= 200000000; i++) { sum += powern (i, i % 5); } std::cout<<"sum = "<<sum<<std::endl; return 0; }
On teste les différents niveaux d'optimisation : O0, O1, O2, O3
for level in O0 O1 O2 O3
do
echo "level:" $level
g++ -$level sum_power.cpp
time ./a.out
echo
done
level: O0 sum = 1.28e+40 1.72 level: O1 sum = 1.28e+40 0.87 level: O2 sum = 1.28e+40 0.38 level: O3 sum = 1.28e+40 0.40
On constate un facteur 5 sur le temps d'exécution entre O0
et O2
(ou O3
).
En manipulant le code intermédiaire, l'étape de génération de code produit du code assembleur : un code texte humainement lisible mais difficile à interpréter (et à écrire directement !) car il est très proche du code machine. Il possède quasiment les mêmes instructions que le code machine qui lui est représenté par des mots (nombres binaires).
Pour exporter la traduction en assembleur d'un fichier source, on utilise l'option -S
:
g++ -S add.cpp # crée le fichier assembleur add.s
pygmentize -g add.s
.file "add.cpp" .text .globl _Z8additionii .type _Z8additionii, @function _Z8additionii: .LFB0: .cfi_startproc endbr64 pushq %rbp .cfi_def_cfa_offset 16 .cfi_offset 6, -16 movq %rsp, %rbp .cfi_def_cfa_register 6 movl %edi, -4(%rbp) movl %esi, -8(%rbp) movl -4(%rbp), %edx movl -8(%rbp), %eax addl %edx, %eax popq %rbp .cfi_def_cfa 7, 8 ret .cfi_endproc .LFE0: .size _Z8additionii, .-_Z8additionii .globl main .type main, @function main: .LFB1: .cfi_startproc endbr64 pushq %rbp .cfi_def_cfa_offset 16 .cfi_offset 6, -16 movq %rsp, %rbp .cfi_def_cfa_register 6 movl $2, %esi movl $1, %edi call _Z8additionii nop popq %rbp .cfi_def_cfa 7, 8 ret .cfi_endproc .LFE1: .size main, .-main .ident "GCC: (Ubuntu 9.3.0-17ubuntu1~20.04) 9.3.0" .section .note.GNU-stack,"",@progbits .section .note.gnu.property,"a" .align 8 .long 1f - 0f .long 4f - 1f .long 5 0: .string "GNU" 1: .align 8 .long 0xc0000002 .long 3f - 2f 2: .long 0x3 3: .align 8 4:
On reconnaît en particulier 2 étiquettes _Z8additionii
et _main
qui représente nos 2 fonctions.
En utilisant la commande nm
, on retrouve ces noms de symboles dans l'exécutable généré :
g++ add.cpp
nm a.out
0000000000004010 B __bss_start 0000000000004010 b completed.8060 w __cxa_finalize@@GLIBC_2.2.5 0000000000004000 D __data_start 0000000000004000 W data_start 0000000000001070 t deregister_tm_clones 00000000000010e0 t __do_global_dtors_aux 0000000000003df8 d __do_global_dtors_aux_fini_array_entry 0000000000004008 D __dso_handle 0000000000003e00 d _DYNAMIC 0000000000004010 D _edata 0000000000004018 B _end 00000000000011d8 T _fini 0000000000001120 t frame_dummy 0000000000003df0 d __frame_dummy_init_array_entry 0000000000002154 r __FRAME_END__ 0000000000003fc0 d _GLOBAL_OFFSET_TABLE_ w __gmon_start__ 0000000000002004 r __GNU_EH_FRAME_HDR 0000000000001000 t _init 0000000000003df8 d __init_array_end 0000000000003df0 d __init_array_start 0000000000002000 R _IO_stdin_used w _ITM_deregisterTMCloneTable w _ITM_registerTMCloneTable 00000000000011d0 T __libc_csu_fini 0000000000001160 T __libc_csu_init U __libc_start_main@@GLIBC_2.2.5 0000000000001141 T main 00000000000010a0 t register_tm_clones 0000000000001040 T _start 0000000000004010 D __TMC_END__ 0000000000001129 T _Z8additionii
Bien que le langage assembleur ne fasse pas l'objet de ce cours, on s'intéresse à un exemple de fichier assembleur (plus simple !) :
pygmentize -g hello.s
.data hello: .asciiz "hello\n" # hello pointe vers "hello\n\0" .text .globl __start __start: li $v0, 4 # la primitive print_string la $a0, hello # a0 l'adresse de hello syscall
Ce fichier peut s'exécuter directement avec spim
:
spim -notrap -file hello.s
SPIM Version 8.0 of January 8, 2010 Copyright 1990-2010, James R. Larus. All Rights Reserved. See the file README for a full copyright notice. hello Attempt to execute non-instruction at 0x0040000c
as
sous linux) qui traduit le langage d’assembleur en langage machine..o
par convention).C'est une étape qui consiste à transformer un ensemble de fichiers objets en un exécutable.
L'éditeur de liens est un programme (ld
sous linux) qui utilise tous les fichiers objets pour fabriquer l’exécutable :
C’est enfin le système d’exploitation qui s'occupera (à la demande de l'utilisateur) de charger en mémoire et lancer le programme.
Lors de l'édition de lien, on distingue :
On peut lister les bibliothèques dynamiques avec la commande ldd
(otool -L
sous MacOS) :
g++ helloworld.cpp
ldd ./a.out
linux-vdso.so.1 (0x00007ffcf01db000) libstdc++.so.6 => /lib/x86_64-linux-gnu/libstdc++.so.6 (0x00007f5a0f56f000) libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f5a0f37d000) libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007f5a0f22e000) /lib64/ld-linux-x86-64.so.2 (0x00007f5a0f75d000) libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1 (0x00007f5a0f213000)
Extrait de C. Bastoul, (Ré)introduction à la compilation.
g++
?¶L'appel à la commande g++
semble faire toutes les étapes du processus complet de compilation.
Comment détailler ces étapes ?
On va d'abord appeller g++
en mode verbeux (option -v
) pour afficher les sous-étapes :
g++ -v helloworld.cpp
Using built-in specs. COLLECT_GCC=g++ COLLECT_LTO_WRAPPER=/usr/lib/gcc/x86_64-linux-gnu/9/lto-wrapper OFFLOAD_TARGET_NAMES=nvptx-none:hsa OFFLOAD_TARGET_DEFAULT=1 Target: x86_64-linux-gnu Configured with: ../src/configure -v --with-pkgversion='Ubuntu 9.3.0-17ubuntu1~20.04' --with-bugurl=file:///usr/share/doc/gcc-9/README.Bugs --enable-languages=c,ada,c++,go,brig,d,fortran,objc,obj-c++,gm2 --prefix=/usr --with-gcc-major-version-only --program-suffix=-9 --program-prefix=x86_64-linux-gnu- --enable-shared --enable-linker-build-id --libexecdir=/usr/lib --without-included-gettext --enable-threads=posix --libdir=/usr/lib --enable-nls --enable-clocale=gnu --enable-libstdcxx-debug --enable-libstdcxx-time=yes --with-default-libstdcxx-abi=new --enable-gnu-unique-object --disable-vtable-verify --enable-plugin --enable-default-pie --with-system-zlib --with-target-system-zlib=auto --enable-objc-gc=auto --enable-multiarch --disable-werror --with-arch-32=i686 --with-abi=m64 --with-multilib-list=m32,m64,mx32 --enable-multilib --with-tune=generic --enable-offload-targets=nvptx-none=/build/gcc-9-HskZEa/gcc-9-9.3.0/debian/tmp-nvptx/usr,hsa --without-cuda-driver --enable-checking=release --build=x86_64-linux-gnu --host=x86_64-linux-gnu --target=x86_64-linux-gnu Thread model: posix gcc version 9.3.0 (Ubuntu 9.3.0-17ubuntu1~20.04) COLLECT_GCC_OPTIONS='-v' '-shared-libgcc' '-mtune=generic' '-march=x86-64' /usr/lib/gcc/x86_64-linux-gnu/9/cc1plus -quiet -v -imultiarch x86_64-linux-gnu -D_GNU_SOURCE helloworld.cpp -quiet -dumpbase helloworld.cpp -mtune=generic -march=x86-64 -auxbase helloworld -version -fasynchronous-unwind-tables -fstack-protector-strong -Wformat -Wformat-security -fstack-clash-protection -fcf-protection -o /tmp/ccDjKHWg.s GNU C++14 (Ubuntu 9.3.0-17ubuntu1~20.04) version 9.3.0 (x86_64-linux-gnu) compiled by GNU C version 9.3.0, GMP version 6.2.0, MPFR version 4.0.2, MPC version 1.1.0, isl version isl-0.22.1-GMP GGC heuristics: --param ggc-min-expand=100 --param ggc-min-heapsize=131072 ignoring duplicate directory "/usr/include/x86_64-linux-gnu/c++/9" ignoring nonexistent directory "/usr/local/include/x86_64-linux-gnu" ignoring nonexistent directory "/usr/lib/gcc/x86_64-linux-gnu/9/include-fixed" ignoring nonexistent directory "/usr/lib/gcc/x86_64-linux-gnu/9/../../../../x86_64-linux-gnu/include" #include "..." search starts here: #include <...> search starts here: /usr/include/c++/9 /usr/include/x86_64-linux-gnu/c++/9 /usr/include/c++/9/backward /usr/lib/gcc/x86_64-linux-gnu/9/include /usr/local/include /usr/include/x86_64-linux-gnu /usr/include End of search list. GNU C++14 (Ubuntu 9.3.0-17ubuntu1~20.04) version 9.3.0 (x86_64-linux-gnu) compiled by GNU C version 9.3.0, GMP version 6.2.0, MPFR version 4.0.2, MPC version 1.1.0, isl version isl-0.22.1-GMP GGC heuristics: --param ggc-min-expand=100 --param ggc-min-heapsize=131072 Compiler executable checksum: 466f818abe2f30ba03783f22bd12d815 COLLECT_GCC_OPTIONS='-v' '-shared-libgcc' '-mtune=generic' '-march=x86-64' as -v --64 -o /tmp/ccScZ4Cf.o /tmp/ccDjKHWg.s GNU assembler version 2.34 (x86_64-linux-gnu) using BFD version (GNU Binutils for Ubuntu) 2.34 COMPILER_PATH=/usr/lib/gcc/x86_64-linux-gnu/9/:/usr/lib/gcc/x86_64-linux-gnu/9/:/usr/lib/gcc/x86_64-linux-gnu/:/usr/lib/gcc/x86_64-linux-gnu/9/:/usr/lib/gcc/x86_64-linux-gnu/ LIBRARY_PATH=/usr/lib/gcc/x86_64-linux-gnu/9/:/usr/lib/gcc/x86_64-linux-gnu/9/../../../x86_64-linux-gnu/:/usr/lib/gcc/x86_64-linux-gnu/9/../../../../lib/:/lib/x86_64-linux-gnu/:/lib/../lib/:/usr/lib/x86_64-linux-gnu/:/usr/lib/../lib/:/usr/lib/gcc/x86_64-linux-gnu/9/../../../:/lib/:/usr/lib/ COLLECT_GCC_OPTIONS='-v' '-shared-libgcc' '-mtune=generic' '-march=x86-64' /usr/lib/gcc/x86_64-linux-gnu/9/collect2 -plugin /usr/lib/gcc/x86_64-linux-gnu/9/liblto_plugin.so -plugin-opt=/usr/lib/gcc/x86_64-linux-gnu/9/lto-wrapper -plugin-opt=-fresolution=/tmp/ccN5YZqi.res -plugin-opt=-pass-through=-lgcc_s -plugin-opt=-pass-through=-lgcc -plugin-opt=-pass-through=-lc -plugin-opt=-pass-through=-lgcc_s -plugin-opt=-pass-through=-lgcc --build-id --eh-frame-hdr -m elf_x86_64 --hash-style=gnu --as-needed -dynamic-linker /lib64/ld-linux-x86-64.so.2 -pie -z now -z relro /usr/lib/gcc/x86_64-linux-gnu/9/../../../x86_64-linux-gnu/Scrt1.o /usr/lib/gcc/x86_64-linux-gnu/9/../../../x86_64-linux-gnu/crti.o /usr/lib/gcc/x86_64-linux-gnu/9/crtbeginS.o -L/usr/lib/gcc/x86_64-linux-gnu/9 -L/usr/lib/gcc/x86_64-linux-gnu/9/../../../x86_64-linux-gnu -L/usr/lib/gcc/x86_64-linux-gnu/9/../../../../lib -L/lib/x86_64-linux-gnu -L/lib/../lib -L/usr/lib/x86_64-linux-gnu -L/usr/lib/../lib -L/usr/lib/gcc/x86_64-linux-gnu/9/../../.. /tmp/ccScZ4Cf.o -lstdc++ -lm -lgcc_s -lgcc -lc -lgcc_s -lgcc /usr/lib/gcc/x86_64-linux-gnu/9/crtendS.o /usr/lib/gcc/x86_64-linux-gnu/9/../../../x86_64-linux-gnu/crtn.o COLLECT_GCC_OPTIONS='-v' '-shared-libgcc' '-mtune=generic' '-march=x86-64'
On se rend compte que g++
est en réalité un pilote de compilateur qui déclenche 3 appels sucessifs :
cc1plus
: le compilateur C++ à proprement parleras
: l'assembleurld
: le lieur (linker)On décompose maintenant les 3 étapes en appelant les commandes correspondantes.
Le code source helloworld.cpp
est converti par cc1plus
en code assembleur helloworld.s
:
# On extraie la ligne appelant 'cc1plus'
g++ -v helloworld.cpp 2>&1 >/dev/null | grep cc1plus
/usr/lib/gcc/x86_64-linux-gnu/9/cc1plus -quiet -v -imultiarch x86_64-linux-gnu -D_GNU_SOURCE helloworld.cpp -quiet -dumpbase helloworld.cpp -mtune=generic -march=x86-64 -auxbase helloworld -version -fasynchronous-unwind-tables -fstack-protector-strong -Wformat -Wformat-security -fstack-clash-protection -fcf-protection -o /tmp/ccn18pBF.s
On constate que le compilateur crée un fichier assembleur .s
temporaire.
On remplace par helloworld.s
et on exécute la commande cc1plus
et ses arguments.
# commande produite par le compilateur g++-8 sur MacOS
/usr/local/Cellar/gcc/8.3.0/libexec/gcc/x86_64-apple-darwin17.7.0/8.3.0/cc1plus \
-quiet \
-D__DYNAMIC__ \
helloworld.cpp \
-fPIC \
-quiet \
-dumpbase helloworld.cpp \
-mmacosx-version-min=10.13.0 \
-mtune=core2 \
-auxbase helloworld \
-o helloworld.s
file helloworld.s
bash: /usr/local/Cellar/gcc/8.3.0/libexec/gcc/x86_64-apple-darwin17.7.0/8.3.0/cc1plus: No such file or directory helloworld.s: cannot open `helloworld.s' (No such file or directory)
À noter que
cc1plus
n'a pas vocation a être appelé directement : son répertoire ne figure pas dans la variable d'environnementPATH
du shell.
Le code assembleur helloworld.s
est converti par as
en code objet helloworld.o
:
# On extraie la ligne appelant 'as'
g++ -v helloworld.cpp 2>&1 >/dev/null | grep "^ as"
as -v --64 -o /tmp/ccdwc5me.o /tmp/cc5jzNei.s
On constate que le compilateur crée un fichier objet .o
temporaire à partir du .s
temporaire.
On remplace par helloworld.o
et helloworld.s
puis on exécute la commande as
et ses arguments.
# commande produite par le compilateur g++-8 sur MacOS
as -arch x86_64 \
-force_cpusubtype_ALL \
-mmacosx-version-min=10.13 \
-o helloworld.o \
helloworld.s
file helloworld.o
Assembler messages: Fatal error: invalid listing option `r' helloworld.o: cannot open `helloworld.o' (No such file or directory)
Le code objet helloworld.o
est lié par ld
en un fichier exécutable a.out
:
# On extraie la ligne appelant 'ld'
g++ -v helloworld.cpp 2>&1 >/dev/null | grep "/usr/bin/ld"
On constate que le compilateur a utilisé le fichier objet .o
temporaire pour créer l'exécutable a.out
.
On remplace par helloworld.o
puis on exécute la commande ld
et ses arguments.
# commande produite par le compilateur g++-8 sur MacOS
/usr/bin/ld -dynamic \
-arch x86_64 \
-macosx_version_min 10.13.0 \
-weak_reference_mismatches non-weak \
-o a.out \
-L/usr/local/lib \
-L. \
-L/usr/local/Cellar/gcc/8.3.0/lib/gcc/8/gcc/x86_64-apple-darwin17.7.0/8.3.0 \
-L/usr/local/Cellar/gcc/8.3.0/lib/gcc/8/gcc/x86_64-apple-darwin17.7.0/8.3.0/../../.. \
helloworld.o \
-lstdc++ \
-no_compact_unwind \
-lSystem \
-lgcc_ext.10.5 \
-lgcc \
-lSystem
file a.out
./a.out
/usr/bin/ld: unrecognised emulation mode: acosx_version_min Supported emulations: elf_x86_64 elf32_x86_64 elf_i386 elf_iamcu elf_l1om elf_k1om i386pep i386pe a.out: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=574e3e5ca8dbb250f1e7dfc79d472bb14a01c37e, for GNU/Linux 3.2.0, not stripped Hello World !
Le compilateur c++
possède un très grand nombre d'options.
Pour clang
, l'option --help
permet de toutes les lister :
echo 'clang++ propose' $(clang++ --help |wc -l) 'options !'
clang++ propose 872 options !
Les passer en revue ne présente pas d'intérêt. Les principales catégories sont :
En général, l'option -march=native
fait ce travail d'ajustement pour vous :
g++ -c -Q -march=native --help=target
The following options are target specific: -m128bit-long-double [enabled] -m16 [disabled] -m32 [disabled] -m3dnow [disabled] -m3dnowa [disabled] -m64 [enabled] -m80387 [enabled] -m8bit-idiv [disabled] -m96bit-long-double [disabled] -mabi= sysv -mabm [enabled] -maccumulate-outgoing-args [disabled] -maddress-mode= long -madx [disabled] -maes [enabled] -malign-data= compat -malign-double [disabled] -malign-functions= 0 -malign-jumps= 0 -malign-loops= 0 -malign-stringops [enabled] -mandroid [disabled] -march= bdver2 -masm= att -mavx [enabled] -mavx2 [disabled] -mavx256-split-unaligned-load [disabled] -mavx256-split-unaligned-store [enabled] -mavx5124fmaps [disabled] -mavx5124vnniw [disabled] -mavx512bitalg [disabled] -mavx512bw [disabled] -mavx512cd [disabled] -mavx512dq [disabled] -mavx512er [disabled] -mavx512f [disabled] -mavx512ifma [disabled] -mavx512pf [disabled] -mavx512vbmi [disabled] -mavx512vbmi2 [disabled] -mavx512vl [disabled] -mavx512vnni [disabled] -mavx512vpopcntdq [disabled] -mbionic [disabled] -mbmi [enabled] -mbmi2 [disabled] -mbranch-cost=<0,5> 2 -mcall-ms2sysv-xlogues [disabled] -mcet-switch [disabled] -mcld [disabled] -mcldemote [disabled] -mclflushopt [disabled] -mclwb [disabled] -mclzero [disabled] -mcmodel= [default] -mcpu= -mcrc32 [disabled] -mcx16 [enabled] -mdispatch-scheduler [disabled] -mdump-tune-features [disabled] -mf16c [enabled] -mfancy-math-387 [enabled] -mfentry [disabled] -mfentry-name= -mfentry-section= -mfma [enabled] -mfma4 [disabled] -mforce-drap [disabled] -mforce-indirect-call [disabled] -mfp-ret-in-387 [enabled] -mfpmath= sse -mfsgsbase [enabled] -mfunction-return= keep -mfused-madd -mfxsr [enabled] -mgeneral-regs-only [disabled] -mgfni [disabled] -mglibc [enabled] -mhard-float [enabled] -mhle [disabled] -miamcu [disabled] -mieee-fp [enabled] -mincoming-stack-boundary= 0 -mindirect-branch-register [disabled] -mindirect-branch= keep -minline-all-stringops [disabled] -minline-stringops-dynamically [disabled] -minstrument-return= none -mintel-syntax -mlarge-data-threshold=<number> 65536 -mlong-double-128 [disabled] -mlong-double-64 [disabled] -mlong-double-80 [enabled] -mlwp [disabled] -mlzcnt [enabled] -mmanual-endbr [disabled] -mmemcpy-strategy= -mmemset-strategy= -mmitigate-rop [disabled] -mmmx [enabled] -mmovbe [enabled] -mmovdir64b [disabled] -mmovdiri [disabled] -mmpx [disabled] -mms-bitfields [disabled] -mmusl [disabled] -mmwaitx [disabled] -mno-align-stringops [disabled] -mno-default [disabled] -mno-fancy-math-387 [disabled] -mno-push-args [disabled] -mno-red-zone [disabled] -mno-sse4 [disabled] -mnop-mcount [disabled] -momit-leaf-frame-pointer [disabled] -mpc32 [disabled] -mpc64 [disabled] -mpc80 [disabled] -mpclmul [enabled] -mpcommit [disabled] -mpconfig [disabled] -mpku [disabled] -mpopcnt [enabled] -mprefer-avx128 -mprefer-vector-width= 128 -mpreferred-stack-boundary= 0 -mprefetchwt1 [disabled] -mprfchw [enabled] -mptwrite [disabled] -mpush-args [enabled] -mrdpid [disabled] -mrdrnd [enabled] -mrdseed [disabled] -mrecip [disabled] -mrecip= -mrecord-mcount [disabled] -mrecord-return [disabled] -mred-zone [enabled] -mregparm= 6 -mrtd [disabled] -mrtm [disabled] -msahf [enabled] -msgx [disabled] -msha [disabled] -mshstk [disabled] -mskip-rax-setup [disabled] -msoft-float [disabled] -msse [enabled] -msse2 [enabled] -msse2avx [disabled] -msse3 [enabled] -msse4 [enabled] -msse4.1 [enabled] -msse4.2 [enabled] -msse4a [enabled] -msse5 -msseregparm [disabled] -mssse3 [enabled] -mstack-arg-probe [disabled] -mstack-protector-guard-offset= -mstack-protector-guard-reg= -mstack-protector-guard-symbol= -mstack-protector-guard= tls -mstackrealign [disabled] -mstringop-strategy= [default] -mstv [enabled] -mtbm [disabled] -mtls-dialect= gnu -mtls-direct-seg-refs [enabled] -mtune-ctrl= -mtune= bdver2 -muclibc [disabled] -mvaes [disabled] -mveclibabi= [default] -mvect8-ret-in-mem [disabled] -mvpclmulqdq [disabled] -mvzeroupper [enabled] -mwaitpkg [disabled] -mwbnoinvd [disabled] -mx32 [disabled] -mxop [disabled] -mxsave [enabled] -mxsavec [disabled] -mxsaveopt [disabled] -mxsaves [disabled] Known assembler dialects (for use with the -masm= option): att intel Known ABIs (for use with the -mabi= option): ms sysv Known code models (for use with the -mcmodel= option): 32 kernel large medium small Valid arguments to -mfpmath=: 387 387+sse 387,sse both sse sse+387 sse,387 Known indirect branch choices (for use with the -mindirect-branch=/-mfunction-return= options): keep thunk thunk-extern thunk-inline Known choices for return instrumentation with -minstrument-return=: call none nop5 Known data alignment choices (for use with the -malign-data= option): abi cacheline compat Known vectorization library ABIs (for use with the -mveclibabi= option): acml svml Known address mode (for use with the -maddress-mode= option): long short Known preferred register vector length (to use with the -mprefer-vector-width= option): 128 256 512 none Known stack protector guard (for use with the -mstack-protector-guard= option): global tls Valid arguments to -mstringop-strategy=: byte_loop libcall loop rep_4byte rep_8byte rep_byte unrolled_loop vector_loop Known TLS dialects (for use with the -mtls-dialect= option): gnu gnu2 Known valid arguments for -march= option: i386 i486 i586 pentium lakemont pentium-mmx winchip-c6 winchip2 c3 samuel-2 c3-2 nehemiah c7 esther i686 pentiumpro pentium2 pentium3 pentium3m pentium-m pentium4 pentium4m prescott nocona core2 nehalem corei7 westmere sandybridge corei7-avx ivybridge core-avx-i haswell core-avx2 broadwell skylake skylake-avx512 cannonlake icelake-client icelake-server cascadelake bonnell atom silvermont slm goldmont goldmont-plus tremont knl knm intel geode k6 k6-2 k6-3 athlon athlon-tbird athlon-4 athlon-xp athlon-mp x86-64 eden-x2 nano nano-1000 nano-2000 nano-3000 nano-x2 eden-x4 nano-x4 k8 k8-sse3 opteron opteron-sse3 athlon64 athlon64-sse3 athlon-fx amdfam10 barcelona bdver1 bdver2 bdver3 bdver4 znver1 znver2 btver1 btver2 generic native Known valid arguments for -mtune= option: generic i386 i486 pentium lakemont pentiumpro pentium4 nocona core2 nehalem sandybridge haswell bonnell silvermont goldmont goldmont-plus tremont knl knm skylake skylake-avx512 cannonlake icelake-client icelake-server cascadelake intel geode k6 athlon k8 amdfam10 bdver1 bdver2 bdver3 bdver4 btver1 btver2 znver1 znver2
Connaître les options de base pour les warnings permet d'éviter des erreurs de programmation et de gagner beaucoup de temps. En général, le compilateur active par défaut un grand nombre de warnings mais c'est insuffisant dans certains cas.
Soit le fichier fonc.cpp
contenant :
pygmentize -g fonc.cpp
int f(int a, int b) { int c; if(c > a) return 1; else return 0; }
g++ -c fonc.cpp
g++
compile ce fichier silencieusement alors qu'il contient du mauvais code :
g++ -Wall -c fonc.cpp
fonc.cpp: In function ‘int f(int, int)’: fonc.cpp:3:3: warning: ‘c’ is used uninitialized in this function [-Wuninitialized] 3 | if(c > a) return 1; | ^~
Ici -Wall
permet de détecter le fait que c
est utilisé sans avoir été initialisé mais il faut ajouter Wextra
pour détecter le paramètre inutilisé dans la signature de la fonction.
g++ -Wall -Wextra -c fonc.cpp
fonc.cpp: In function ‘int f(int, int)’: fonc.cpp:1:18: warning: unused parameter ‘b’ [-Wunused-parameter] 1 | int f(int a, int b) { | ~~~~^ fonc.cpp:3:3: warning: ‘c’ is used uninitialized in this function [-Wuninitialized] 3 | if(c > a) return 1; | ^~
Nous avons déjà eu un aperçu des options d'optimisation :
-On avec n=0,1,2,3,s,fast
Attention, en fonction du compilateur, un niveau élevé peut augmenter significativement le temps de compilation et altérer la correction du résultat ! $\rightarrow$ en phase de débuggage, il faut baisser le niveau d'optimisation.
Les options de debuggage vont ajouter des informations utiles pour les débuggueurs :
-gn avec n=0,1,2,3 (`2` par défaut)
On reviendra dessus dans la partie du cours consacrée au débuggage.
ar
libmoncontenu.a
Prenons pour exemple l'ensemble de fichiers sources contenus dans maillage/
:
tree maillage
maillage ├── maillage.hpp ├── main.cpp ├── Makefile ├── Makefile_1 ├── Makefile_2 ├── Makefile_3 ├── Makefile_4 ├── Makefile_a ├── Makefile_arch ├── point.cpp ├── point.hpp ├── polygone.cpp ├── polygone.hpp ├── quadrangle.cpp ├── quadrangle.hpp ├── segment.cpp ├── segment.hpp ├── triangle.cpp └── triangle.hpp 0 directories, 19 files
Compilons d'abord en utilisant le Makefile
du répertoire.
cd $ROOTDIR
cd maillage
make clean
make
rm -f main.e *.o *.a *.d g++ -std=c++11 -Wall -o main.o -c main.cpp g++ -std=c++11 -Wall -o point.o -c point.cpp g++ -std=c++11 -Wall -o segment.o -c segment.cpp g++ -std=c++11 -Wall -o triangle.o -c triangle.cpp g++ -std=c++11 -Wall -o quadrangle.o -c quadrangle.cpp g++ -std=c++11 -Wall -o polygone.o -c polygone.cpp g++ -std=c++11 -Wall -o main.e main.o point.o segment.o triangle.o quadrangle.o polygone.o
La compilation a créé un exécutable en liant les fichiers objets main.o point.o segment.o triangle.o quadrangle.o polygone.o
.
Une autre façon de faire consiste à regrouper d'abord les fichiers objets (excepté main.o
) dans une bibliothèque .a
:
ar r libmaillage.a point.o segment.o triangle.o quadrangle.o polygone.o
file libmaillage.a
ar: creating libmaillage.a libmaillage.a: current ar archive
On fait ensuite l'édition de lien avec cette bibliothèque libmaillage.a
:
c++ -std=c++11 -Wall -o main.e main.o -L. -lmaillage
./main.e
Test cout sur maillage : Type de mailles : triangles Nombre max de voisins : 3 Nombre de noeuds : 4 Nombre de mailles : 2 Tests unitaires réussis, maillage : 4/4
Remarques :
- Notez la syntaxe du nom de bibliothèque derrière l'option
-l
:maillage
pour un fichier bibliothèque qui s'appellelibmaillage.a
- le fichier
main.o
doit être placé avant l'invocation de la bibliothèque-lmaillage
On peut lister le contenu de cette bibliothèque statique :
ar t libmaillage.a
point.o segment.o triangle.o quadrangle.o polygone.o
L'intérêt d'une bibliothèque est de pouvoir accéder à du contenu de code depuis une autre application.
Par exemple, on se place dans un répertoire extérieur au projet maillage
.
cd $ROOTDIR/test
pygmentize test_maillage.cpp
#include "quadrangle.hpp" #include "maillage.hpp" int main() { maillage<quadrangle, 4> m; m.all_testsu(); return 0; }
On compile d'abord le fichier test_maillage.cpp
en indiquant le répertoire où se trouvent les fichiers d'en-têtes inclus (ici quadrangle.hpp
et maillage.hpp
).
c++ -std=c++11 -Wall -I../maillage -c test_maillage.cpp
file test_maillage.o
test_maillage.o: ELF 64-bit LSB relocatable, x86-64, version 1 (SYSV), not stripped
On compile maintenant en liant avec la bibliothèque libmaillage.a
et on exécute le programme :
c++ -std=c++11 -Wall -I./maillage/ -o test_maillage.e test_maillage.o \
-L../maillage -lmaillage
./test_maillage.e
Test cout sur maillage : Type de mailles : triangles Nombre max de voisins : 3 Nombre de noeuds : 4 Nombre de mailles : 2 Tests unitaires réussis, maillage : 4/4
Notez l'argument -L../maillage
qui indique à l'éditeur de lien qu'il doit ajouter le répertoire ../maillage/
à la liste des répertoires où il va rechercher le fichier libmaillage.a
.
On a vu :
Au programme du prochain chapitre : les gestionnaires de projets, c'est-à-dire les outils qui vont appeler pour vous le compilateur pour produire efficacement un ou plusieurs exécutables à partir de votre code source.