Programmation C source/préprocesseur

Un livre de Wikilivres.
Programmation C source
Programmation C++
Programmation C++
Sommaire
Modifier ce modèle
/* Le langage préprocesseur est un langage de macro
   analysé avant la compilation.

   Une macro est l'association d'un texte de remplacement
   à un identificateur.

   Indépendant du C le langage préprocesseur pourrait être utilisé
   avec d'autres langages de programmation.
   Il ignore totalement la structure du programme en C. */

/* Les directives sont évaluées de haut en bas
   elles commencent par #
   suivi d'un nombre quelconque d'espaces ou tabulations
   suivi du nom de la directive en minuscule.
   Les directives doivent être déclarées sur une ligne dédiée.

   #define	définit un motif de substitution
   #undef	retire un motif de substitution
   #include	inclusion de fichier
   #ifdef, #ifndef, #if, #else, #elif, #endif	compilation conditionnelle
   #pragma	extension du compilateur
   #error	message d'erreur et arrêt de la compilation
*/

#include <stdio.h>
/* inclut stdio.h qui décrit
   - la fonction printf */

#define TAILLE 100

int main(void)
{
	printf("--------------------\n");
	printf(" Déclarations de\n");
	printf("  constantes\n");
	printf("--------------------\n");

	printf("constante TAILLE = %d\n", TAILLE);
	/* sera transformé en printf("constante TAILLE = %d\n", 100);
           avant la compilation */


	#define PREFIXE "erreur:"
	#define FORMULE (1 + 1)
 
	printf(PREFIXE "%d\n", FORMULE);
	/* sera transformé en printf("erreur:" "%d\n", (1 + 1));
	   avant la compilation
	   (rappel: en C on peut écrire une chaîne en plusieurs fois) */


	#define BLOC_DEFAUT            \
	    default:                   \
	        puts("Cas interdit."); \
	        break;
	/* équivalent à
	   #define BLOC_DEFAUT default: puts("Cas interdit."); break;
	   mais sur plusieurs ligne grâce à \ */

	int var = 3;
	switch (var)
	{
	    case 1:
	        puts("choix 1");
	        break;
	    case 2:
	        puts("choix 2");
	        break;
	    BLOC_DEFAUT
	}


	#define TAILLE 100
	// aucun avertissement, inutile TAILLE est déjà remplacé par 100

	#define TAILLE 3
	/* déclenche un avertissement du compilateur
	   redéfinition de TAILLE */


	/* à savoir !
	   utiliser autant que possible une énumération */
	enum jours_de_la_semaine { lundi, mardi, mercredi };
	enum jours_de_la_semaine variable_jour_courant;
	variable_jour_courant = lundi;
	// plutôt que
	#define LUNDI 0
        variable_jour_courant = LUNDI;
        /* est Recommandé !
	   notamment pour le débogage */


	printf("\n--------------------\n");
	printf(" Déclarations\n");
	printf("  automatiques\n");
	printf("  de constantes\n");
	printf("--------------------\n");

	/* en C sont déclarés automatiquement:
	   __FILE__  de type (char *)  nom du fichier courant
	   __LINE__  de type (int)     numéro de la ligne en cours
	   __DATE__  de type (char *)  jour, mois, année
	   __TIME__  de type (char *)  heures, minutes, secondes
	   __STDC__  de type (int)     C ANSI (sans les spécificités du compilateur)
	*/

	printf(__FILE__ "\n");
	printf("ligne = %d\n", __LINE__);
	printf(__DATE__ "\n");
	printf(__TIME__ "\n");
	#ifdef __STDC__
		printf("le compilateur travaille en C norme ANSI\n");
	#else
		printf("le compilateur ne travaille PAS en C norme ANSI\n");
	#endif
	/* si __STDC__ a été défini, c'est à dire a une valeur quelconque
	   alors le compilateur travaille en C ANSI
	   sinon le compilateur ne travaille PAS en C ANSI */


	printf("\n--------------------\n");
	printf(" Extensions\n");
	printf("--------------------\n");

	/* des constantes supplémentaires
           sont disponibles suivant le compilateur
	   on retrouve le plus souvent celle ci et aussi d'autres: */

	/* Détection du système d'exploitation
	   _WIN32 ou __WIN32__ (Windows)
	   linux ou __linux__ (Linux)
	   __APPLE__ ou __MACH__ (Apple Darwin)
	   __FreeBSD__ (FreeBSD)
	   __NetBSD__ (NetBSD)
	   sun ou __SVR4 (Solaris)

	   exemple : */
	#if defined(linux) || defined(__linux__)
		printf("le système d'exploitation est gnu linux\n");
	#endif


	/* Détection des compilateurs
	Visual C++ : _MSC_VER
	Compilateur gcc :
		Version : __GNUC__, __GNUC_MINOR__, __GNUC_PATCHLEVEL__.

	Compilateurs Borland :
		__TURBOC__ (version de Turbo C ou Borland C)
		__BORLANDC__ (version de Borland C++ Builder)

	Divers : __MINGW32__ (MinGW), __CYGWIN__ ou __CYGWIN32__ (Cygwin).

	exemple : */
	#ifdef __GNUC__
		printf("gcc version : %d.%d.%d\n", __GNUC__, __GNUC_MINOR__, __GNUC_PATCHLEVEL__);
	#endif


	printf("\n--------------------\n");
	printf(" Déclaration\n");
	printf(" de macros\n");
	printf("--------------------\n");

	/* Une macro est en fait une constante
	   qui peut prendre un certain nombre d'arguments.
	   Les arguments sont placés entre parenthèses
	   après le nom de la macro sans espaces */

	#define SWAP(x,y) x ^= y; y ^= x; x ^= y
	/* échange la valeur des deux arguments
	   (qui doivent être des variables entières) */

	char a = 'A', b = 'B';
	printf("a = %c, b = %c\n", a, b);
	SWAP(a, b);
	// échange les valeurs de a et b
	printf("a = %c, b = %c\n", a, b);


	int c = 2, d = 1;
	if (c < d) SWAP(c, d);
	// sera remplacé avant compilation par :
	if (c < d) c ^= d; d ^= c; c ^= d;
	/* problème !
	   seule l'expression c ^= d
	   sera dans la condition if (c < d) */
	printf("c = %d, d = %d\n", c, d);

	#define SWAP(x,y) { x ^= y; y ^= x; x ^= y; }
	// voilà comment on corrige SWAP
	c = 2, d = 1;
	if (c < d) SWAP(c, d);
	printf("c = %d, d = %d\n", c, d);


	#define MAX(x,y) x > y ? x : y
	// retourne le maximum

	printf("\nmaximum = %d\n", MAX(4, 6));
	// sera remplacé avant compilation par :
	printf("maximum = %d\n", 4 > 6 ? 4 : 6);


	int i;
	i = 2 * MAX(4,6);
	// sera remplacé avant compilation par :
	i = 2 * 4 > 6 ? 4 : 6;
	printf("i = %d\n", i);
	/* résultat :
	   i devrait etre égal à 2 * 6 soit 12
	   pourtant ce qui s’exécute est
	   8 > 6 ? 4 : 6
	   et on obtient 4 ! */

	#define MAX(x,y) ((x) > (y) ? (x) : (y))
	// voilà comment on corrige MAX
	i = 2 * MAX(4, 6);
	printf("i = %d\n", i);


	int e, f = 2, g = 1;
	e = MAX(++f,g);
        printf("e = %d\n", e);

	f = 2; g = 1;
	// lors de la compilation on a :
	e = ((++f) > (g) ? (++f) : (g));
	printf("e = %d\n", e);
	/* on s'attend à avoir 3 comme résultat
	   mais c'est 4 !
	   ++f est exécuté 2 fois */

	/* Dans ce cas on ne peut pas utiliser de macro
	   il est alors préférable d'utiliser une fonction inline */
	inline int maxi(int a, int b)
	{
		return a > b ? a : b;
	}
	f = 2; g = 1;
	e = maxi(++f, g);
	printf("e = %d\n", e);


	printf("\n--------------------\n");
	printf(" Suppression\n");
	printf(" d'une définition\n");
	printf("--------------------\n");

	#undef TAILLE
	#undef MAX
	#undef SWAP


	printf("\n--------------------\n");
	printf(" Transformation\n");
	printf(" en chaîne de\n");
	printf(" caractères\n");
	printf("--------------------\n");

	#define assert(condition) \
		if( (condition) == 0 ) \
		{ \
			puts( "La condition " #condition " a échoué" ); \
			exit( 1 ); \
		}
	/* avec une macro qui prend au moins 1 argument
	   pour utiliser l'argument sous forme de texte
	   on peut utiliser #argument  */

//--------------------------------------------------------------------------------
	printf("\n--------------------\n");
	printf(" Concaténation\n");
	printf(" d'arguments\n");
	printf("--------------------\n");

	#define version(symbole) symbole ## _v123
	int version(variable);
	// Déclare int variable_v123;


	printf("\n--------------------\n");
	printf(" Macros\n");
        printf(" à nombre variable\n");
        printf(" d'arguments\n");
	printf("--------------------\n");

	#define debug(format, ...) fprintf( stderr, "Dans " __FILE__ " ligne %d " format "\n", __LINE__, __VA_ARGS__ )

	debug("erreur %s %s %s", "argument 1", "argument 2", "etc.");
	/* valide en C version C99 uniquement
	   ... doivent être remplacés par un au minimum ou plusieurs arguments */


	#define debug(format, ...) fprintf( stderr, "Dans " __FILE__ " ligne %d " format "\n", __LINE__, ##__VA_ARGS__ )

	debug("erreur");
	/* valide avec le compilateur gcc
	   ##__VA_ARGS__ 0 ou plusieurs arguments */


	printf("\n--------------------\n");
	printf(" Autres exemples\n");
	printf("--------------------\n");

	void printf_sizeof(char * type, size_t taille)
	{
		printf("sizeof(%s) = %zd.\n", type, taille);
	}

	printf_sizeof("char", sizeof(char));

	#define PRINTF_SIZEOF(type) printf("sizeof(" #type ") = %zd.\n", sizeof(type))

	PRINTF_SIZEOF(char);
	// on préfère la macro !


	enum etat_t { Arret, Demarrage, Marche, ArretEnCours } etat = Marche;
 
	#define CASE_ETAT(etat) case etat: printf("Dans l'état " #etat "\n"); break;
	 
	switch (etat)
	{
		CASE_ETAT(Arret)
		CASE_ETAT(Demarrage)
		CASE_ETAT(Marche)
		CASE_ETAT(ArretEnCours)
		default:
			printf("Etat inconnu (%d).\n", etat);
			break;
	}


	printf("\n--------------------\n");
	printf(" Compilation\n");
	printf(" conditionnelle\n");
	printf("--------------------\n");

	#ifdef DEBUG
	// S'utilise : debug( ("Variable x = %d\n", x) ); (double parenthésage)
	#define debug(x) printf x
	#else
	#define debug(x)
	#endif
	/* si DEBUG a une valeur quelconque
	   alors debug(x) sera remplacé avant compilation par printf x */

	int varX = 12345;
	debug( ("1) Variable varX = %d\n", varX) );

	#define DEBUG 1

	#ifdef DEBUG
	// S'utilise : debug( ("Variable x = %d\n", x) ); (double parenthésage)
	#define debug(x) printf x
	#else
	#define debug(x)
	#endif

	debug( ("2) Variable varX = %d\n", varX) );


	#if defined(DEBUG) && defined(PAS_DE_DEBUG)
	#error DEBUG et PAS_DE_DEBUG sont définis !
	#endif
	/* voilà comment tester si plusieurs constantes ont une valeur quelconque
	   (tester que les constantes sont définies) */


	#if 0
	// 0 vaut faux donc les lignes entre #if et #endif sont ignorées

	// Vous pouvez mettre ce que vous voulez ici, tout sera ignoré, même du code invalide
	:-)
	#endif


	#if 0
	// ce qui suit est ignoré

		#include "fichier.h"
		/* inclut un fichier qui se trouve
		   d'abord dans le même répertoire que notre fichier de travail
		   ou sinon dans les répertoires préconfigurés */

		#include <fichier.h>
		/* inclut un fichier qui se trouve dans les répertoire préconfigurés
		   c'est à dire /usr/include sous linux
		   et ceux passés explicitement en paramètre au compilateur. */

		#define FICHIER "un_fichier.h"
		#include FICHIER
		// fonctionne aussi

		/* un fichier .h contient normalement
		   des déclarations de type, macros, prototypes de fonctions
		   relatif à un module

		   et pas du code
		   parce que ces fichiers sont destinés à être inclus
		   dans plusieurs endroits du programme
		   et qu'il pourrait y avoir redéfinition (définition en double)
		   et donc erreurs */

	#endif


	printf("\n--------------------\n");
	printf(" Protection contre\n");
	printf(" les inclusions\n");
	printf(" multiples\n");
	printf("--------------------\n");

	#ifndef H_MON_FICHIER
	#define H_MON_FICHIER
 
	// Mettre toutes les déclarations ici
 
	#endif
	/* explication : si H_MON_FICHIER n'est pas défini
	   définir H_MON_FICHIER
	   continuer avec la suite

	   la prochaine fois tout sera ignoré car H_MON_FICHIER sera déjà défini */

	printf("\n--------------------\n");
	printf(" Avertissement et\n");
	printf(" message d'erreur\n");
	printf(" personnalisés\n");
	printf("--------------------\n");

	#if 0
		#error message
		// affiche le message d'erreur et arrête la compilation
	#endif

	#warning message
	// fonctionne avec gcc

	return 0;
}