Introduction : Un petit détail qui peut vous coûter des secondes de compilation
\n\nImaginez : vous écrivez un projet C++. Le travail bat son plein, le code s'allonge, les dossiers de fichiers se multiplient. Et là, vous tapez à nouveau #include. Comme d'habitude — entre chevrons ? Ou entre guillemets ? Quelle différence, après tout ? Le compilateur est intelligent, il trouvera tout seul.
Indice : il trouvera. Mais pas toujours rapidement. Et pas toujours le fichier que vous aviez en tête. La question de la différence entre #include <filename> et #include "filename" est l'une des plus populaires sur Stack Overflow (des centaines de milliers de vues). Et il ne s'agit pas seulement de sucre syntaxique. C'est une question de performance de compilation, de contrôle des dépendances et, en fin de compte, de la qualité de votre code.
Dans cet article, nous ne nous contenterons pas d'énumérer les règles. Nous allons regarder sous le capot du préprocesseur, analyser des exemples concrets et donner des conseils pratiques pour accélérer la construction de votre projet. C'est parti !
\n\nComment fonctionne le préprocesseur ? Anatomie de la directive #include
\n\nAvant que le compilateur ne voie votre code, le préprocesseur entre en jeu. Sa tâche est de préparer le « source » pour la compilation. La directive #include est une commande : « Prends le contenu du fichier X et insère-le ici, à la place de cette ligne ».
Cela semble simple. Mais le diable, comme toujours, se cache dans les détails. À savoir, où le préprocesseur cherche-t-il ce fichier X.
\n\nLes chevrons : #include <filename>
\nLorsque vous écrivez #include <iostream>, le préprocesseur part à la recherche exclusivement dans les chemins système standards. Sous Linux, ce sont généralement /usr/include, /usr/local/include et les chemins transmis au compilateur via le drapeau -I (ou /I dans Visual Studio).
Point clé : le préprocesseur NE regarde PAS dans le répertoire courant du projet. Uniquement dans les dossiers qui lui ont été indiqués comme « système » ou « pour la recherche d'includes ».
\nC'est fait exprès : pour ne pas attraper accidentellement un fichier local portant le même nom que la bibliothèque standard.
\n\nLes guillemets : #include "filename"
\nAvec les guillemets, l'histoire est plus complexe et plus intéressante. La norme C++ (section 16.2) dit : le comportement est défini par l'implémentation. Mais en pratique, 99% des compilateurs (GCC, Clang, MSVC) fonctionnent selon le même principe :
\n- \n
- D'abord la recherche dans le répertoire où se trouve le fichier qui inclut (généralement — le dossier courant du source). \n
- Ensuite la recherche dans les mêmes chemins que pour les chevrons (système +
-I). \n
Autrement dit, #include "myheader.h" vérifiera d'abord le dossier de votre main.cpp. Trouvé — parfait. Pas trouvé — il suit la liste système.
Est-ce juste une connaissance ou une véritable optimisation ?
\n« Et alors ? — demanderez-vous. — Les ordinateurs sont rapides, quelle différence cela fait-il de chercher où que ce soit ? »
\nLa différence existe, et elle se compose de deux facteurs : la vitesse de recherche et la clarté des intentions.
\n\nFacteur 1 : Vitesse de recherche des fichiers
\nDans un petit projet (10-20 fichiers), vous ne remarquerez pas la différence. Mais dans un véritable produit d'entreprise ou un moteur de jeu, le nombre d'includes peut dépasser les dizaines de milliers. Chaque fois que vous écrivez #include "some_system_lib.h", le préprocesseur perd un temps supplémentaire à vérifier le dossier courant et tous les sous-dossiers (selon l'implémentation).
Exemple concret : imaginez que vous ayez un projet avec 10 000 fichiers. 20% d'entre eux utilisent des guillemets pour les bibliothèques standards. Chacun de ces includes effectue un appel système supplémentaire stat() ou access() dans le répertoire courant. Ce sont des microsecondes, mais au total pour l'ensemble du projet — de véritables secondes de compilation.
Utiliser #include <iostream> au lieu de #include "iostream" garantit que la recherche commence directement par les chemins système, en contournant le répertoire local. Cela donne