.png Annexe 1--Suivi pas à pas d'inclusions multiples

Annexe 1--Suivi pas à pas d'inclusions multiples

Le problème des inclusions multiples, mentionné dans le document principal, trouve sa solution en C++ par l'utilisation de directives d'inclusion conditionnelle, jointes aux définitions de symboles.

Cet annexe présente, pas à pas, un exemple concret où les fichiers source du projet effectuent une inclusion multiple d'un même fichier d'en-tête, et démontre comment s'en sortent les programmeuses et les programmeurs.

Reprenons un exemple de projet connu, mais avec une légère modification:

Ici, "bits.h" est inclus par "octets.h" et par "UnProjet.cpp".

La différence, d'apparence mineure, est importante.

C'est là une situation fort plausible: on n'a qu'à penser que les prototypes de "octets.h" auraient besoin d'une constante ou d'un type se trouvant dans "bits.h", par exemple.

Puisque inclure un fichier signifie en inclure le texte en entier de ce fichier, alors "octets.h" contient le texte complet de "bits.h". Et si "UnProjet.cpp" inclut "octets.h" et "bits.h", alors "UnProjet.cpp" inclut "bits.h" deux (2) fois. Le compilateur n'aimera pas ça du tout.

Le texte qui suit sera notre "bits.h" avant l'ajout de directives d'inclusion conditionnelle:


typedef unsigned char tPositionBit;
bool Lire_Bit (unsigned short Mot, tPositionBit Position);
void Forcer_Bit (unsigned short &Mot,
                 tPositionBit Position,
                 bool Valeur);

Selon le schéma plus haut, "UnProjet.cpp" se retrouverait (une fois les directives "#include" dans "octets.h" et dans "UnProjet.cpp" résolues) avec--entre autres--deux définitions du type "tPositionBit", ce qui est... vilain.

Par souci de clarté, supposons que le fichier "octets.h" ressemble à ceci:


// octets.h
#include "bits.h"
// contenu de octets.h

... et que celui de "UnProjet.cpp", à son tour, ressemble à:


// UnProjet.cpp
#include "octets.h" // <== [1]
#include "bits.h" // contenu de UnProjet.cpp

Maintenant, voici notre "bits.h" une fois les directives d'inclusion conditionnelle judicieusement insérées:


// bits.h
#ifndef BITS_H
#define BITS_H
typedef unsigned char tPositionBit;
bool Lire_Bit (unsigned short Mot, tPositionBit Position);
void Forcer_Bit (unsigned short &Mot,
                 tPositionBit Position,
                 bool Valeur);
#endif // not defined BITS_H

À défaut d'être une grande différence lexicale, on vient tout juste de changer radicalement la mécanique de la compilation de fichiers incluant "bits.h". Voici comment.

Étape 1: inclusion de "octets.h" dans "UnProjet.cpp"

Le premier fichier à être inclus dans "UnProjet.cpp", lu de haut en bas, est "octets.h". Le texte de "octets.h" est donc inséré dans "UnProjet.cpp":


// UnProjet.cpp
// octets.h
#include "bits.h" // <==
// contenu de octets.h
#include "bits.h"
// contenu de UnProjet.cpp
Étape 2: inclusion de "bits.h" dans "UnProjet.cpp"

Ensuite, le préprocesseur devra inclure le fichier "bits.h", puisque le texte de "octets.h", maintenant inclus dans "UnProjet.cpp", le demande.

On obtient donc:


// UnProjet.cpp
// octets.h
// bits.h
#ifndef BITS_H // <==
#define BITS_H
typedef unsigned char tPositionBit;
bool Lire_Bit (unsigned short Mot, tPositionBit Position);
void Forcer_Bit (unsigned short &Mot,
                 tPositionBit Position,
                 bool Valeur);
#endif // not defined BITS_H
// contenu de octets.h
#include "bits.h"
// contenu de UnProjet.cpp
Étape 3: poursuite du traitement dans "UnProjet.cpp"

Poursuivant son travail, le préprocesseur rencontrera le directive "#ifndef BITS_H". Puisque le symbole "BITS_H" n'est pas encore défini, le code qui suit (jusqu'au "#endif") sera inclus dans la compilation.

La première ligne de ce code conditionnellement inclus est "#define BITS_H", ce qui définit le symbole en question. Par la suite, le texte de "octets.h" sera traité lui aussi (nous en faisons ici abstraction par souci de simplicité).

Étape 4: deuxième inclusion de "bits.h" dans "UnProjet.cpp"

Par la suite, le préprocesseur devra inclure à nouveau "bits.h", cette fois parce que le texte de "UnProjet.cpp" le demande.

On obtiendra ce qui suit:


// UnProjet.cpp
// octets.h
// bits.h
#ifndef BITS_H
#define BITS_H
typedef unsigned char tPositionBit;
bool Lire_Bit (unsigned short Mot, tPositionBit Position);
void Forcer_Bit (unsigned short &Mot,
                 tPositionBit Position,
                 bool Valeur);
#endif // not defined BITS_H
// contenu de octets.h
// bits.h
#ifndef BITS_H // <==
#define BITS_H
typedef unsigned char tPositionBit;
bool Lire_Bit (unsigned short Mot, tPositionBit Position);
void Forcer_Bit (unsigned short &Mot,
                 tPositionBit Position,
                 bool Valeur);
#endif // not defined BITS_H
// contenu de UnProjet.cpp
Étape 5: suite et fin du traitement de "UnProjet.cpp"

Poursuivant son travail, le préprocesseur rencontrera pour une deuxième fois "#ifndef BITS_H", mais cette fois-ci la condition "not defined BITS_H" sera fausse, puisque le symbole "BITS_H" aura été défini précédemment--lors de la première inclusion du fichier d'en-tête "bits.h".

Allez hop! Tour de magie! Le code considéré pour la compilation se limitera donc à:


// UnProjet.cpp
// octets.h
// bits.h
#ifndef BITS_H // est VRAI à ce moment-ci
#define BITS_H // BITS_H est maintenant défini!
typedef unsigned char tPositionBit;
bool Lire_Bit (unsigned short Mot, tPositionBit Position);
void Forcer_Bit (unsigned short &Mot,
                 tPositionBit Position,
                 bool Valeur);
#endif // not defined BITS_H
// contenu de octets.h
// bits.h
#ifndef BITS_H // est FAUX à ce moment-ci
#endif // not defined BITS_H
// contenu de UnProjet.cpp

Cette manoeuvre est tellement commune que les utilitaires "sorciers" (en anglais, les "wizards") de VC génèrent les directives appropriées à cette fin spontanément lors de la création de certains fichiers d'en-tête.

Exercice: étant donné les fichiers d'en-tête suivants

a.h

b.h

c.h


#define X
int v;

#ifndef X
#define X
int v;
#endif

#ifndef X
int v;
#endif

Pour chacun des fichiers source ci-après, cochez la case à droite du nom de fichier si et seulement si sa compilation se fera correctement.


f1.cpp        

f2.cpp        

f3.cpp        

#include "a.h"
#include "b.h"
int main () { }

#include "b.h"
#include "a.h"
int main () { }

#include "c.h"
#include "c.h"
int main () { }

f4.cpp        

f5.cpp        

f6.cpp        

#include "b.h"
#include "c.h"
int main () { }

#include "b.h"
#include "b.h"
int main () { }

#include "c.h"
#include "b.h"
int main () { }

[1] Nous utiliserons ce symbole (<==) pour montrer là où le préprocesseur en est rendu dans son traitement...