🔝 Retour au Sommaire
Chapitre 2 · Section 2.1 · Installation des compilateurs · Niveau Débutant
GCC et Clang ne sont pas des produits concurrents au sens commercial du terme : ce sont deux projets open source qui implémentent le même standard (ISO C++) et qui se poussent mutuellement vers le haut depuis plus de quinze ans. Le résultat, pour les développeurs, est un écosystème remarquablement robuste où chaque compilateur excelle dans des domaines différents.
Cette section propose une comparaison détaillée et concrète, illustrée par des exemples, pour vous aider à comprendre les forces de chacun et à adopter la stratégie professionnelle qui consiste à utiliser les deux.
C'est le critère qui frappe en premier, surtout quand on débute. Prenons un programme contenant une erreur volontaire :
#include <string>
#include <vector>
int main() {
std::vector<std::string> words = {"hello", "world"};
int n = words; // Erreur : impossible de convertir un vector en int
}Sortie de Clang 20 :
error: no viable conversion from 'std::vector<std::string>' to 'int'
int n = words;
^ ~~~~~
note: candidate constructor (implicit copy constructor) not viable:
no known conversion from 'vector<string>' to 'int'
Clang identifie la colonne exacte de l'erreur avec un caret (^), souligne l'expression fautive avec des tildes (~~~~~) et explique pourquoi la conversion échoue en citant les candidats rejetés.
Sortie de GCC 15 :
error: conversion from 'std::vector<std::__cxx11::basic_string<char>>' to
non-scalar type 'int' requested
6 | int n = words;
| ^~~~~
GCC signale la même erreur et pointe aussi la bonne colonne (les versions récentes ont adopté le caret). Cependant, le message expose le type interne complet (std::__cxx11::basic_string<char> au lieu du plus lisible std::string), ce qui peut dérouter un débutant.
C'est sur les erreurs impliquant des templates imbriqués que l'écart se creuse le plus. Considérons une tentative d'appeler std::sort sur un conteneur dont les éléments ne sont pas comparables :
#include <algorithm>
#include <vector>
struct Widget {};
int main() {
std::vector<Widget> v(10);
std::sort(v.begin(), v.end());
}Clang produit un message structuré qui identifie clairement que Widget ne satisfait pas les contraintes requises par std::sort, en citant l'opérateur manquant (operator<). GCC produit un message fonctionnellement équivalent mais souvent plus long, avec une pile d'instanciation de templates qui peut atteindre plusieurs dizaines de lignes sur du code fortement générique.
Avec l'arrivée des Concepts (C++20), les deux compilateurs ont fait un bond en lisibilité : quand une contrainte requires échoue, le message indique directement quelle contrainte n'est pas satisfaite plutôt que de dérouler une cascade d'instanciations. C'est une raison de plus de préférer le C++ moderne.
Clang est particulièrement généreux en suggestions automatiques :
#include <cstdio>
int main() {
int x = 42;
printf("%s\n", x); // %s attend un char*, pas un int
}Clang suggère :
warning: format specifies type 'char *' but the argument has type 'int'
printf("%s\n", x);
~~ ^
%d
Il propose le remplacement %d directement en face du %s fautif. GCC émet un avertissement similaire mais ne propose pas toujours la correction inline de manière aussi explicite.
Clang reste un cran au-dessus en clarté, en concision et en qualité des suggestions, particulièrement pour le code template-heavy. GCC a considérablement progressé depuis la version 13 (introduction du format de diagnostic amélioré, meilleur support des couleurs, suggestions de #include manquants), mais l'avantage de Clang demeure sensible.
Conseil pratique : si un message d'erreur GCC vous semble incompréhensible, recompilez le même fichier avec Clang. Souvent, le diagnostic Clang suffit à comprendre le problème.
Les deux compilateurs produisent du code machine hautement optimisé. Les benchmarks publics (comme ceux du Phoronix Test Suite) montrent que les écarts de performance oscillent typiquement entre -5 % et +5 % selon le type de charge, sans vainqueur systématique.
GCC tend à exceller sur :
- La vectorisation automatique de boucles numériques complexes. L'optimiseur de GCC est historiquement très agressif dans la détection de patterns vectorisables et génère du code SIMD (SSE, AVX, AVX-512) efficace.
- Les optimisations mathématiques avec
-ffast-mathou-Ofast, où GCC applique des transformations algébriques que Clang traite avec plus de prudence. - Le déroulement de boucles (loop unrolling) dans des cas imbriqués où GCC prend des décisions plus agressives.
Clang tend à exceller sur :
- L'inlining inter-procédural agressif, où le front-end modulaire de LLVM permet de meilleures décisions sur les fonctions à intégrer.
- Les optimisations à la liaison (LTO — Link-Time Optimization) via ThinLTO, un mode LTO incrémental plus rapide que le LTO « full » de GCC tout en restant très efficace.
- La taille du code (
-Os,-Oz) : Clang produit généralement des binaires plus compacts quand on optimise pour la taille.
Considérons une boucle simple de calcul vectoriel :
void add_arrays(float* __restrict__ dst,
const float* __restrict__ a,
const float* __restrict__ b,
int n) {
for (int i = 0; i < n; ++i)
dst[i] = a[i] + b[i];
}Les deux compilateurs vectorisent cette boucle avec -O2. Pour observer l'assembleur généré et comparer :
g++-15 -O2 -march=native -S -o add_gcc.s add_arrays.cpp
clang++-20 -O2 -march=native -S -o add_clang.s add_arrays.cpp Vous pouvez aussi utiliser Compiler Explorer (godbolt.org) pour visualiser l'assembleur côte à côte dans votre navigateur, sans rien installer. C'est un outil indispensable pour comprendre ce que le compilateur fait réellement de votre code — nous y reviendrons au chapitre 48.
Les micro-benchmarks sur des fonctions isolées sont trompeurs. Ce qui compte, c'est la performance de votre application, avec votre profil de charge. Le chapitre 35 (Benchmarking avec Google Benchmark) vous montrera comment mesurer de manière fiable. En attendant, retenez simplement que les deux compilateurs sont au coude à coude et que le choix du compilateur a rarement autant d'impact que le choix de l'algorithme ou de la structure de données.
Sur un projet de taille moyenne à grande, Clang compile typiquement 10 à 30 % plus vite que GCC. L'écart se creuse sur du code intensif en templates (bibliothèques header-only, code générique lourd) et en instanciations — des situations courantes en C++ moderne.
Cet avantage vient en partie de l'architecture du front-end LLVM, conçu dès le départ pour la performance de compilation, et en partie du linker lld (projet LLVM), sensiblement plus rapide que le linker GNU ld par défaut.
Une manière simple de comparer les temps de compilation :
# Avec GCC
time g++-15 -std=c++23 -O2 -c mon_fichier.cpp -o /dev/null
# Avec Clang
time clang++-20 -std=c++23 -O2 -c mon_fichier.cpp -o /dev/nullPour un projet complet utilisant CMake, configurez deux répertoires de build distincts et comparez :
# Build GCC
cmake -B build-gcc -G Ninja -DCMAKE_CXX_COMPILER=g++-15
time cmake --build build-gcc
# Build Clang (avec lld pour la liaison)
cmake -B build-clang -G Ninja \
-DCMAKE_CXX_COMPILER=clang++-20 \
-DCMAKE_EXE_LINKER_FLAGS="-fuse-ld=lld"
time cmake --build build-clangPendant le développement, vous compilez des dizaines de fois par heure. Gagner 20 % sur chaque cycle de build représente un confort quotidien non négligeable. C'est l'une des raisons pour lesquelles beaucoup de développeurs utilisent Clang au quotidien et réservent GCC aux builds de validation (CI, release).
Combiné avec ccache (section 2.3) et le générateur Ninja (section 2.5 / 26.5), la chaîne Clang + lld + ccache + Ninja offre des temps de recompilation remarquablement courts.
L'un des atouts les plus déterminants de Clang n'est pas le compilateur lui-même, mais l'infrastructure LLVM sur laquelle il repose. LLVM a été conçu dès le départ comme une plateforme modulaire, et cette modularité a donné naissance à un ensemble d'outils de développement qui partagent tous le même front-end d'analyse syntaxique :
clangd — un serveur de langage implémentant le protocole LSP (Language Server Protocol). Il fournit à votre éditeur l'autocomplétion sémantique, les diagnostics en temps réel, la navigation dans le code (go to definition, find references), et le renommage intelligent. clangd est aujourd'hui le standard de facto pour l'édition C++ dans VS Code, Neovim, Emacs et Sublime Text.
clang-tidy — un analyseur statique et moderniseur de code. Il détecte des centaines de patterns problématiques (bugs potentiels, mauvaises pratiques, code non idiomatique) et peut appliquer des corrections automatiques. Par exemple, il peut convertir des boucles C classiques en range-based for, remplacer des new/delete par des smart pointers, ou détecter des captures de lambda dangereuses.
clang-format — un formateur de code automatique configurable. En combinaison avec un fichier .clang-format à la racine du projet, il garantit un style cohérent dans toute la base de code, indépendamment des préférences individuelles des développeurs.
Sanitizers — AddressSanitizer (ASan), UndefinedBehaviorSanitizer (UBSan), ThreadSanitizer (TSan) et MemorySanitizer (MSan) ont été développés dans le cadre du projet LLVM avant d'être portés dans GCC. Les deux compilateurs les supportent aujourd'hui via la même interface (-fsanitize=...), mais l'implémentation LLVM est souvent un pas en avance sur les fonctionnalités les plus récentes.
lld — le linker LLVM, nettement plus rapide que le linker GNU ld sur les gros projets. Il peut réduire les temps de liaison de 2× à 5× selon la taille du binaire.
lldb — le debugger LLVM, alternative à GDB. Moins répandu sur Linux (où GDB reste dominant), mais bien intégré à l'écosystème LLVM et à certains IDE.
GCC dispose aussi d'outils propres, mais de manière moins structurée :
- gcov / lcov — outils de couverture de code, bien établis et largement utilisés (chapitre 34) ;
- gprof — profiler basé sur l'instrumentation (chapitre 31) ;
- libstdc++ debug mode — mode de débogage de la bibliothèque standard activable via
-D_GLIBCXX_DEBUG, qui détecte les utilisations invalides de conteneurs et d'itérateurs à l'exécution ; - Les sanitizers partagés avec LLVM (
-fsanitize=address, etc.).
En revanche, GCC n'offre pas d'équivalent intégré à clangd, clang-tidy ou clang-format. En pratique, même les développeurs qui compilent avec GCC utilisent les outils LLVM pour l'édition et l'analyse de code.
L'écosystème LLVM crée une synergie naturelle : clangd utilise le même parser que clang++, donc les diagnostics que vous voyez dans votre éditeur sont exactement ceux que le compilateur produira. Avec GCC, il y a un décalage possible entre ce que clangd (outil LLVM) vous montre et ce que g++ produit réellement — un point à garder en tête si un warning apparaît dans l'IDE mais pas à la compilation, ou inversement.
Le compilateur et la bibliothèque standard sont des composants séparés. La bibliothèque standard fournit les conteneurs (std::vector, std::map...), les algorithmes (std::sort, std::find...), les utilitaires (std::string, std::optional...) et tout ce qui vit dans le namespace std.
libstdc++ est l'implémentation du projet GNU, étroitement liée à GCC. C'est la bibliothèque standard par défaut sur quasiment toutes les distributions Linux. Elle est mature, très optimisée et testée en production depuis des décennies.
libc++ est l'implémentation du projet LLVM. Elle tend à adopter les nouvelles fonctionnalités du standard plus rapidement, avec un design interne parfois plus propre, mais elle est moins répandue sur Linux.
| Configuration | Bibliothèque standard |
|---|---|
g++ sur Ubuntu |
libstdc++ (toujours) |
clang++ sur Ubuntu (défaut) |
libstdc++ |
clang++ -stdlib=libc++ |
libc++ (si installée) |
Sur Ubuntu, Clang utilise libstdc++ par défaut, exactement comme GCC. Cela signifie que les binaires produits par les deux compilateurs sont généralement ABI-compatibles : ils peuvent se lier entre eux sans problème, ce qui est essentiel pour les bibliothèques précompilées de la distribution.
Pour utiliser libc++ avec Clang, il faut l'installer explicitement et le spécifier :
sudo apt install libc++-20-dev libc++abi-20-dev
clang++-20 -stdlib=libc++ -std=c++23 mon_programme.cpp -o mon_programme Dans le cadre de cette formation, nous restons sur libstdc++, qui est le choix par défaut et le plus compatible avec l'écosystème Ubuntu. L'utilisation de libc++ se justifie dans des cas spécifiques : développement de la suite LLVM elle-même, projets visant macOS/iOS (où libc++ est le défaut), ou besoin d'une fonctionnalité C++23/26 implémentée dans libc++ mais pas encore dans libstdc++.
Chaque nouveau standard ISO C++ (publié tous les trois ans : C++11, C++14, C++17, C++20, C++23, C++26) apporte des fonctionnalités de langage et de bibliothèque que les compilateurs doivent ensuite implémenter. Historiquement, le schéma se répète :
- GCC propose souvent un support expérimental très tôt, parfois avant même la ratification du standard, et atteint un support complet parmi les premiers.
- Clang suit de près, avec un décalage de quelques mois à un an selon les fonctionnalités.
- En 2026, les deux compilateurs sont suffisamment proches pour que le choix du compilateur ne soit plus un facteur limitant dans l'adoption d'un standard récent.
Le flag -std= spécifie la version du standard à utiliser :
# C++17 — support complet partout
g++-15 -std=c++17 program.cpp
clang++-20 -std=c++17 program.cpp
# C++20 — support complet partout
g++-15 -std=c++20 program.cpp
clang++-20 -std=c++20 program.cpp
# C++23 — support complet en mars 2026
g++-15 -std=c++23 program.cpp
clang++-20 -std=c++23 program.cpp
# C++26 — support partiel, en cours d'implémentation
g++-15 -std=c++2c program.cpp # alias provisoire
clang++-20 -std=c++2c program.cpp # alias provisoire 💡 L'alias
-std=c++2c(pour « C++ de la décennie 202x, deuxième itération ») est le nom provisoire utilisé pendant l'implémentation. Une fois le support plus mature, les compilateurs acceptent aussi-std=c++26. Les deux formes sont équivalentes.
Par défaut, GCC active ses extensions GNU (qui ajoutent des fonctionnalités non standard). Pour rester strictement conforme au standard ISO :
g++-15 -std=c++23 program.cpp # Active les extensions GNU
g++-15 -std=c++23 -pedantic program.cpp # Avertit sur les extensions Clang a un comportement similaire. En mode -std=c++23 (sans le préfixe gnu++), les extensions les plus courantes restent disponibles mais des avertissements sont émis avec -Wpedantic. Pour un code maximalement portable, compilez toujours avec -Wpedantic sur les deux compilateurs.
GCC est distribué sous GNU General Public License v3. Cette licence copyleft implique que toute modification du compilateur lui-même doit être redistribuée sous la même licence. En revanche, le code que vous compilez avec GCC n'est pas affecté par cette licence — une exception explicite (la GCC Runtime Library Exception) garantit que vos binaires ne sont pas contaminés.
GCC est gouverné par le projet GNU et supervisé par le GCC Steering Committee, un petit groupe de mainteneurs expérimentés.
LLVM utilise la licence Apache 2.0 avec exception LLVM, une licence permissive. Cette licence facilite l'intégration de composants LLVM dans des produits propriétaires, ce qui explique pourquoi de nombreuses entreprises (Apple, Google, Sony, ARM, Intel…) investissent massivement dans le projet.
La gouvernance de LLVM repose sur la LLVM Foundation, une organisation à but non lucratif, avec un modèle de contribution ouvert.
En tant qu'utilisateur des compilateurs, la licence n'affecte pas votre code ni vos binaires. La différence se manifeste surtout pour les entreprises qui veulent embarquer ou modifier le compilateur lui-même : la licence permissive de LLVM rend cette démarche plus simple juridiquement.
Les deux compilateurs sont multi-plateformes et supportent un large éventail de cibles :
GCC supporte un nombre impressionnant d'architectures : x86/x86-64, ARM (32 et 64 bits), RISC-V, MIPS, PowerPC, SPARC, s390x, et de nombreuses autres cibles embarquées. C'est souvent le seul compilateur disponible sur des architectures de niche.
Clang couvre les principales architectures (x86/x86-64, ARM, AArch64, RISC-V, MIPS, PowerPC, WebAssembly) et excelle en cross-compilation : sa conception modulaire permet de cibler une architecture différente simplement en passant un flag --target, sans installer une instance séparée du compilateur. GCC, de son côté, nécessite traditionnellement un build dédié (un cross-compiler) pour chaque cible.
# Cross-compilation vers AArch64 avec Clang (un seul binaire clang suffit)
clang++-20 --target=aarch64-linux-gnu -std=c++23 program.cpp -o program_arm64
# Cross-compilation vers AArch64 avec GCC (nécessite le paquet dédié)
sudo apt install g++-15-aarch64-linux-gnu
aarch64-linux-gnu-g++-15 -std=c++23 program.cpp -o program_arm64 | Critère | GCC (g++) | Clang (clang++) |
|---|---|---|
| Diagnostics | Bons (nets progrès depuis v13) | Excellents, plus lisibles et concis |
| Suggestions de correction | Présentes | Plus fréquentes et plus précises |
| Performance du binaire | Excellent (léger avantage en vectorisation) | Excellent (léger avantage en inlining) |
| Taille du binaire (-Os) | Bon | Souvent plus compact |
| Vitesse de compilation | Bonne | 10-30 % plus rapide |
| Linker associé | GNU ld (lent) / gold | lld (très rapide) |
| Écosystème d'outils | gcov, gprof, debug mode | clangd, clang-tidy, clang-format, lld, lldb |
| Bibliothèque standard | libstdc++ (intégrée, mature) | libc++ ou libstdc++ |
| Support standards (rapidité) | Souvent premier | Suit de très près |
| Cross-compilation | Nécessite un cross-compiler dédié | Flag --target sur un seul binaire |
| Licence | GPLv3 + Runtime Exception | Apache 2.0 + LLVM Exception |
| Architectures supportées | Très large (incluant le niche) | Large (focus sur les principales) |
La pratique professionnelle dominante en 2026 peut se résumer en trois principes :
1. Éditez avec l'outillage LLVM. Configurez clangd comme serveur de langage dans votre IDE (VS Code, Neovim, CLion). Utilisez clang-format pour le formatage automatique et clang-tidy pour l'analyse statique continue. Ces outils fonctionnent indépendamment du compilateur que vous utilisez pour le build final.
2. Développez avec Clang, validez avec GCC. Au quotidien, compilez avec clang++ pour profiter de ses diagnostics et de sa vitesse. Dans votre pipeline CI, ajoutez un job GCC (et idéalement un job avec les deux compilateurs) pour détecter les écarts de comportement et maximiser la portabilité.
3. Déployez avec le meilleur. Pour les builds de production, benchmarkez votre application avec les deux compilateurs et choisissez celui qui donne les meilleures performances mesurées. Ne prenez pas cette décision sur des impressions ou des benchmarks génériques — mesurez sur votre charge réelle.
Cette stratégie tire le meilleur de chaque compilateur sans jamais vous enfermer dans l'un d'eux.
⏭️ État 2026 : GCC 15 et Clang 20 — Nouveautés et support C++26