adresses croissantes
9. Les fichiers
10.10 Compilation conditionnelle
Les mécanismes de compilation conditionnelle ont pour but de compiler ou d’ignorer certaines parties du programme, en fonction d’un test effectué à la compilation. La commande du préprocesseur permettant de réaliser la compilation conditionnelle est la commande #if qui peut prendre plusieurs formes :
• Le #if simple. Quand le préprocesseur rencontre :
#if condition
ensemble_lignes_de_code
#endif
il évalue la condition. Si la condition est vraie, les lignes de code sont compilées sinon, elles sont ignorées.
• Le #if avec #else. Le else permet de compiler deux blocs de lignes différents suivant l’état de la condition :
#if condition
ensemble_lignes_de_code_1
#else
ensemble_lignes_de_code_2
#endif
• Le #if avec #elif. Pour réaliser plusieurs branches, on utilise le #elif qui est équivalent à un else if.
#if condition1
ensemble_lignes_de_code_1
#elif condition2
ensemble_lignes_de_code_2
#elif condition3
ensemble_lignes_de_code_3
#else
ensemble_lignes_de_code_4
#endif
Dans les 3 cas précédents, il est possible de remplacer les #if par des #ifdef ou
#ifndef :
#ifdef nom_de_macro ou bien
#ifndef nom_de_macro
Dans ce cas, le test porte sur la définition ou non d’une macro (et non sur sa valeur). #ifdef nom_de_macro est équivalent à « si la macro est définie » et #ifndef nom_de_macro est équivalent à « si la macro n’est pas définie ». Une autre possibilité existe, l’utilisation de l’opérateur defined associé à un #if. Elle est similaire au #ifdef, mais elle permet en plus de tester plusieurs macros en même temps.
#if defined(macro1) || defined(macro2)
Un cas classique d’utilisation de la compilation conditionnelle est d’éviter plusieurs inclusions d’un même fichier d’entête .h. Cela arrive notamment dans le cas d’un projet complexe comprenant plusieurs fichiers sources. Pour être certain d’inclure un fichier une seule fois, il suffit d’utiliser le mécanisme suivant :
/* fichier exemple.h */
#ifndef EXEMPLE_H
#define EXEMPLE_H
/* corps du fichier exemple.h */
...
#endif
Dans cet exemple, le fichier exemple.h ne peut être inclus qu’une fois dans un source. En effet, la macro EXEMPLE_H n’est définie que dans ce fichier. Lors du premier passage du préprocesseur, la variable étant indéfinie, celui-ci analyse l’intégralité du fichier et définit la macro. Si ce fichier est à nouveau inclus dans le source par erreur, le préprocesseur ne le lira pas car la macro EXEMPLE_H existe déjà. Il faut noter que l’on aurait aussi pu utiliser un defined :
/* fichier exemple.h */
#if !defined(EXAMPLE_H)
#define EXEMPLE_H
/* corps du fichier exemple.h */
...
#endif
La commande #error permet d’afficher un message d’erreur lors de la compilation. Cette commande a pour intérêt de vérifier des conditions permettant au programme de s’exécuter sur ce système d’exploitation. Voici un exemple où on vérifie que la taille des entiers est suffisante :
#include <limits.h>
#if INT_MAX < 1000000
#error " entiers trop petits sur cette machine"
#endif
Une des applications traditionnelle de la compilation conditionnelle est l’adaptation d’un programme à un environnement comme WINDOWS ou LINUX. Par exemple, dans le projet image, la commande système permettant la copie d’un fichier était différente entre les deux systèmes d’exploitation. La macro WIN32 étant systématiquement définie sous Visual C++, il suffit donc d’écrire les lignes suivantes pour régler définitivement le problème :
#ifdef WIN32
system("copy toto tutu");
#else
system("cp toto tutu");
#endif
De la même manière, il est parfois commode d’avoir une version d’un programme en mode Debug avec beaucoup de printf pour surveiller l’évolution des variables et une version normale sans ces printf. Voici par exemple, le projet mastermind :
main() {
int ref[NBCHIFFRES], essai[NBCHIFFRES];
int NbPos, NbChif, NbCoup, i, status;
NbCoup = 0;
tirage(ref, NBCHIFFRES);
#ifdef DEBUG
for (i = 0 ; i < NBCHIFFRES ; i++) printf("ref[%d]=%d", i, ref[i]);
#endif
do {
do {
status = entree(essai, NBCHIFFRES);
} ...
Sous Linux, si vous compilez le programme avec :
cc -DDEBUG master.c -o master
vous allez visualiser le tirage alors que si vous compilez normalement :
cc master.c -o master
le tirage n’apparaît plus. Il est à noter qu’avec Visual C++, une variable _DEBUG est définie automatiquement par le compilateur quand vous êtes en mode Debug mais qu’elle ne l’est plus quand vous êtes en mode Release (version finale).
Exercice 10.15 : le fichier suivant est un exemple réel de fichier d’entête, limits.h. Il indique à Visual C++ les limitations numériques des variables qui dépendent du système d’exploitation.
Expliquez son fonctionnement. Faites attention à bien repérer les paires #if ... #endif.
/***
*limits.h - implementation dependent values
*
* Copyright(c)1985-1997, Microsoft Corporation. All rights reserved.
*
* Purpose:
* Contains defines for a number of implementation dependent values
* which are commonly used in C programs. [ANSI]
****/
#if _MSC_VER > 1000
#pragma once
#endif
#ifndef _INC_LIMITS
#define _INC_LIMITS
#if !defined(_WIN32) && !defined(_MAC)
#error ERROR: Only Mac or Win32 targets supported!
#endif
#define CHAR_MAX UCHAR_MAX
#endif /* _CHAR_UNSIGNED */
#define MB_LEN_MAX 2 /* max. # bytes in multibyte char */
#define LONG_MIN (-2147483647L - 1) /* minimum (signed) long value */
#define LONG_MAX 2147483647L /* maximum (signed) long value */
#define ULONG_MAX 0xffffffffUL /* maximum unsigned long value */
#if _INTEGRAL_MAX_BITS >= 8
#define _I32_MIN (-2147483647i32-1) /* minimum signed 32 bit value */
#define _I32_MAX 2147483647i32 /* maximum signed 32 bit value */
#define _UI32_MAX 0xffffffffui32 /* maximum unsigned 32 bit value */
#endif
#if _INTEGRAL_MAX_BITS >= 64 /* minimum signed 64 bit value */
#define _I64_MIN (-9223372036854775807i64 - 1) /* maximum signed 64 bit value */
#define _I64_MAX 9223372036854775807i64 /* maximum unsigned 64 bit value */
#define _UI64_MAX 0xffffffffffffffffui64
#endif
#if _INTEGRAL_MAX_BITS >= 128 /* minimum signed 128 bit value */
#define _I128_MIN (-170141183460469231731687303715884105727i128 - 1) /* maximum signed 128 bit value */
#define _I128_MAX 170141183460469231731687303715884105727i128 /* maximum unsigned 128 bit value */
#define _UI128_MAX 0xffffffffffffffffffffffffffffffffui128
#endif
#ifdef _POSIX_
#define _POSIX_ARG_MAX 4096
#define _POSIX_CHILD_MAX 6
#define _POSIX_LINK_MAX 8
#define _POSIX_MAX_CANON 255
#define _POSIX_MAX_INPUT 255
#define _POSIX_NAME_MAX 14
#define _POSIX_NGROUPS_MAX 0
#define _POSIX_OPEN_MAX 16
#define _POSIX_PATH_MAX 255
#define _POSIX_PIPE_BUF 512
#define _POSIX_SSIZE_MAX 32767
#define _POSIX_STREAM_MAX 8
#define _POSIX_TZNAME_MAX 3
#define ARG_MAX 14500 /* 16k heap, minus overhead */