// C / C++

Le préprocesseur C : macros et énumérations

Avant même que votre code C ne soit compilé, un outil discret le réécrit : le préprocesseur. Comprendre son fonctionnement, c’est démystifier les #include, écrire des macros propres, et découvrir des astuces puissantes comme les X-macros pour tenir synchronisées ses énumérations. Cet article prolonge l’un des tutoriels les plus commentés de l’histoire de Coder Studio.

Une étape avant la compilation

Le préprocesseur effectue de la substitution de texte pure. Il ne comprend rien au C : il applique mécaniquement des directives commençant par #. Vous pouvez voir son résultat avec gcc -E fichier.c.

#include et #define

#include <stdio.h>      // copie-colle le contenu du fichier
#define PI 3.14159      // remplace chaque PI par 3.14159
#define MAX 100

Règle d’hygiène : une constante #define est un simple texte, sans type ni portée. En C++ moderne, on lui préfère constexpr. Mais en C, les macros restent incontournables.

Macros à arguments — et leurs pièges

#define CARRE(x) ((x) * (x))

int a = CARRE(3);        // 9
int b = CARRE(1 + 2);    // 9 grâce aux parenthèses !

Les parenthèses autour de chaque argument ET autour de l’expression entière sont vitales. Sans elles, CARRE(1+2) deviendrait 1+2*1+2 = 5. C’est le piège n°1 des macros. Autre danger : les effets de bord — CARRE(i++) incrémente i deux fois.

Compilation conditionnelle

#ifdef DEBUG
    printf("Valeur : %d\n", x);
#endif

#ifndef HEADER_H     // garde d'inclusion (include guard)
#define HEADER_H
// ... contenu du header ...
#endif

Les gardes d’inclusion évitent qu’un même header soit inclus deux fois. Le pragma non-standard mais universel #pragma once fait la même chose plus brièvement.

L’astuce des X-macros pour les énumérations

Voici la technique qui faisait tout l’intérêt de l’article d’origine. Problème classique : garder synchronisées une enum et le tableau de chaînes qui la décrit. Oubliez d’ajouter une entrée dans l’un des deux, et tout se décale. La solution : une seule source de vérité.

// La liste, définie UNE seule fois
#define LISTE_COULEURS \
    X(ROUGE) X(VERT) X(BLEU)

// 1) on génère l'enum
#define X(nom) nom,
enum Couleur { LISTE_COULEURS };
#undef X

// 2) on génère le tableau de chaînes — impossible de désynchroniser
#define X(nom) #nom,
const char* noms[] = { LISTE_COULEURS };
#undef X

Ajoutez X(JAUNE) à la liste, et l’enum comme le tableau se mettent à jour ensemble. L’opérateur # transforme un argument en chaîne (« stringification »), et ## permet de concaténer des identifiants. Ces techniques, à manier avec parcimonie, révèlent la vraie puissance — et les dangers — du préprocesseur.


Le préprocesseur est la première des quatre étapes détaillées dans notre guide du langage C. Pour passer aux abstractions plus sûres du C++, voyez le RAII et le pattern Factory.