Anda di halaman 1dari 71

Guide de style du langage C

www.cambien.com
Guide de Style
du Langage C
Cyril Cambien
cyril@cambien.com
Guide de Style du Langage C
ii
SOMMAIRE
1 Introduction ..................................................................................... 1
2 Mise en page du code ...................................................................... 3
Rgles dindentation............................................................................................................... 3
Privilgier les petites fonctions .............................................................................................. 4
Eviter les fonctions trop larges............................................................................................... 5
Arer le code .......................................................................................................................... 6
Allger le code........................................................................................................................ 7
3 Rgles de nommage......................................................................... 8
Nommage en gnral.............................................................................................................. 8
Nommage des fonctions ......................................................................................................... 9
Nommage des macros, des types et des constantes................................................................ 9
Nommage des variables, paramtres et champs................................................................... 10
4 Dclarations................................................................................... 12
Dclarations des constantes.................................................................................................. 12
Dclarations des types avec typedef................................................................................ 12
typedef pour les types standards.................................................................................. 12
typedef pour les structures, les unions et les types numrs...................................... 12
typedef pour les pointeurs de fonctions....................................................................... 12
Ne jamais utiliser #define la place de typedef ..................................................... 13
Dclarations des fonctions.................................................................................................... 13
Dclarations des variables .................................................................................................... 15
Dclaration des variables.................................................................................................. 15
Initialisation des variables ................................................................................................ 15
Dclaration des tableaux .................................................................................................. 15
Variables globales ............................................................................................................ 16
Utilisation dune variable pour deux buts diffrents ........................................................ 16
Utilisation de const ........................................................................................................... 16
5 Structures de contrle.................................................................... 18
Attention aux points-virgules ............................................................................................... 18
Le else dans les if imbriqus........................................................................................... 18
Les tests monstrueux ............................................................................................................ 19
Le switch .......................................................................................................................... 20
La boucle infinie................................................................................................................... 21
Les modifications intempestives des indices de boucles...................................................... 22
Utilisation du goto ............................................................................................................. 22
Les goto inutiles................................................................................................................. 25
6 Fichiers den-tte (includes).......................................................... 27
Factoriser les dfinitions ...................................................................................................... 27
Pas de chemins absolus ........................................................................................................ 27
Guillemets ou <> ? ............................................................................................................... 27
Utiliser les prototypes dfinis dans les includes................................................................... 27
Prvenir la double inclusion et prvoir C++ ....................................................................... 27
Des includes autonomes ................................................................................................. 28
Sommaire
iii
7 Gestion des erreurs en C................................................................ 29
Dtection et traitement des erreurs .......................................................................................29
Utilisation de errno............................................................................................................32
Cast des appels de fonctions en void..................................................................................33
8 Stratgies pour aider la mise au point ........................................ 34
Les assertions........................................................................................................................34
Utiliser les warnings .............................................................................................................35
Libration des ressources......................................................................................................36
Utilisation doutils de vrification ........................................................................................36
9 Problmes de portabilit................................................................ 37
NULL....................................................................................................................................37
Oprateurs de dcalage .........................................................................................................37
Commentaires la C++ ........................................................................................................37
Taille des types entiers..........................................................................................................37
Ordre des octets lintrieur dun entier ..............................................................................38
Prfrez les fonctions standards............................................................................................38
Alignements dans les structures............................................................................................39
10 Bugs Classiques............................................................................. 40
Les macros (1).......................................................................................................................40
Les macros (2).......................................................................................................................41
Les macros (3).......................................................................................................................41
La vrit est ailleurs..............................................................................................................42
= nest pas ==........................................................................................................................42
Commentaire mal ferm .......................................................................................................43
C est gourmand .....................................................................................................................43
a[i] = i++; ne fonctionne pas !.....................................................................................44
Ordre dvaluation des sous expressions ..............................................................................44
Ordre dvaluation des paramtres .......................................................................................45
int a = 1000, b = 1000; long c = a * b; ne fonctionne pas ! ..................................................45
Grer les dpassements de capacit ......................................................................................45
Ne pas confondre *p++ et (*p)++ ....................................................................................46
Tableau de pointeurs ou pointeur de tableaux ? ...................................................................46
Pointeur constant ou constante pointe ?..............................................................................46
Distinction pointeurs/tableaux ..............................................................................................47
Prcision du calcul flottant....................................................................................................47
Fonction retournant une valeur alloue sur la pile................................................................48
11 Optimisations................................................................................. 50
Optimisation vs Lisibilit......................................................................................................50
Cibler les optimisations ........................................................................................................50
Valider les optimisations ......................................................................................................50
Quelques techniques .............................................................................................................50
Sortir les calculs des boucles ............................................................................................50
Inversion boucle/test .........................................................................................................51
Optimisation de la condition de sortie de boucle..............................................................51
Fusion de boucle ...............................................................................................................51
Droulage de boucles (1) ..................................................................................................52
Droulage de boucles (2) ..................................................................................................52
Inversion de boucles imbriques.......................................................................................52
Guide de Style du Langage C
iv
Tester en premier les cas les plus courants....................................................................... 53
Tester en premier les cas les moins coteux tester........................................................ 54
Utiliser un switch la place dun if ........................................................................... 54
Utilisation des pointeurs pour accder aux tableaux........................................................ 55
Prcalcul dans des tables .................................................................................................. 56
Utilisation de memset, memcpy et memmove avec les tableaux .................................. 57
Enlever les register.................................................................................................... 58
12 Ralisation de Composants Logiciels ........................................... 59
Quest ce quun composant logiciel ? .................................................................................. 59
Nommage des fonctions et types exports par une librairie................................................. 59
Le principe de la "bote noire".............................................................................................. 59
Comment cacher les structures de donnes.......................................................................... 60
Htrognit des runtimes C entre les modules.................................................................. 61
Implmentation diffrentes des structures........................................................................ 62
L'allocateur mmoire........................................................................................................ 63
Le futur des composants logiciels ........................................................................................ 64
13 Curiosits du C.............................................................................. 65
a[b] quivalent b[a]............................................................................................................ 65
Le dispositif de Duff (Duffs device) ................................................................................... 65
Les squences Trigraph........................................................................................................ 66
14 Bibliographie ................................................................................. 67
Guide de style du Langage C
1
1 INTRODUCTION
Le langage C est trs proche de la machine et peut tre vu comme une sorte de langage
assembleur universel : les notions de mmoire globale, de pile, de pointeurs sont essentiels
pour dvelopper en C. Ceci fait de C est langage trs efficace et aussi trs puissant et ce nest
pas un hasard si les systmes dexploitation sont gnralement crits en langage C. Dun autre
cot, comme le langage C nest pas un langage de trs haut niveau, ce nest pas un langage
facile car le dveloppeur est responsable de beaucoup de chose. Par exemple, en langage Java,
un dveloppeur na jamais se proccuper de librer de la mmoire alloue devenue inutile,
car Java prvoit un dispositif, le ramasseur de miettes (garbage collector) qui va rcuprer
cette mmoire automatiquement. En C, cest le dveloppeur qui est responsable de cette
mmoire et qui doit la librer explicitement et, sil ne le fait pas, cela a une rpercussion sur
les performances du programme.
Q : Alors pourquoi dvelopper en langage C si des langages comme Java peuvent
mviter davoir grer les problmes de gestion mmoire ?
R : Cette gestion plus perfectionne de la mmoire un cot non ngligeable en
temps de calcul puisque le systme doit dterminer de lui-mme quelle est la mmoire
qui nest plus utilise par le programme. Cest une des raisons pour lesquelles un
programme Java ne sera jamais aussi efficace quun programme C.
Lefficacit du C se fait aussi au prix dun manque total de contrle de la validit des
oprations effectues lors de lexcution dun programme. Par exemple, dans le code suivant :
void ModifieCaractere(char *string,char value, int index)
{
string[index]=value ;
}
void Bug()
{
char toto[]= "bonjour" ;
ModifieCaractere(toto, a, 2000) ;
}
aucun mcanisme navertit que lon accde au 2001
ime
lment dun tableau ne comportant
que 8 lments. Par consquent, le comportement du programme est ici absolument
imprvisible : il peut planter ou avoir un comportement aberrant.
Q : Je dveloppe un programme interactif en PowerBuilder. Certes, C est trs
efficace, mais pourquoi membter faire du C alors que lutilisateur est bien plus
lent que son ordinateur ?
R : Sans doute ce type dapplication peut tre avantageusement dvelopp en
PowerBuilder, mais ny a-t-il pas dans cette application un traitement qui fait
attendre lutilisateur ? Dans ce cas, seul ce traitement pourra tre dvelopp avec
des fonctions C qui seront appeles depuis PowerBuilder.
Guide de Style du Langage C
2
Afin dobtenir un bon niveau de qualit logicielle, le dveloppeur C doit redoubler de rigueur
et djouer tous les piges. Ce guide propose des mthodes pour dvelopper :
un code plus robuste dans lequel les erreurs sont vites dentre de jeu ou, au pire,
dtectes le plus rapidement possible
un code plus lisible car un code clair permet de localiser les problmes plus facilement
un code plus portable pour viter de devoir (trop) rviser un programme lors dun
changement de machine ou de systme dexploitation.
un code plus optimis pour profiter au mieux de lefficacit du C
Ce guide de style nest pas un cours ni un manuel de rfrence sur le langage C, mais il
sadresse au contraire aux dveloppeurs C, y compris aux experts, afin quils amliorent la
qualit de leurs applications.
Guide de style du Langage C
3
2 MISE EN PAGE DU CODE
Rgles dindentation
Il existe plusieurs types dindentation du code C et les partisans de chaque mthode
saffrontent lors de terribles guerres de religion. En fait, le plus important est dadopter un
style unique au sein dun mme groupe de travail et de le respecter.
Si vous navez pas encore choisi un style particulier, vous pouvez utiliser les rgles
suivantes :
- Les accolades du corps dune dfinition de fonction sont toujours isoles sur la colonne de
gauche.
- Les autres accolades ouvrantes, situes aprs les mots-cls if, while, for, do, else
ou switch, sont toujours en fin de ligne, sur la mme ligne que le mot-cl.
- Les accolades fermantes correspondantes sont alignes en dessous de la premire lettre du
mot-cl correspondant. Elles sont isoles sur leur ligne sauf :
- dans le cas dune fin de bloc if suivi par un else.
- dans le cas dune fin de bloc do suivi par son while.
- Des if/else en cascade peuvent se mettre les un sous les autres
Quelques exemples :
Guide de Style du Langage C
4
int ExempleIndentation()
{
if (<test>) {
while (<test>) {
instruction;
for (i=0 ;i<n ;i++) {
instruction;
instruction;
}
instruction;
}
} else if (<test> {
do {
switch (<valeur>) {
case 1 :
instruction;
instruction;
break;
case 2 :
instruction;
break;
default :
instruction;
}
} while (<test>);
} else {
instruction;
instruction;
}
}
Q : Jai appris programmer en langage Pascal et je naime pas les accolades ;
alors comme je suis nostalgique, je me suis dfini ces deux macros :
#define begin {
#define end }
Sympa non ?
R : NON ! Pensez aux dveloppeurs C qui travaillent avec vous sils doivent un jour
reprendre votre code la sauce Pascal
Privilgier les petites fonctions
Eviter les fonctions trop longues (qui comporte trop de lignes) ou trop larges (trop de
colonnes).
Idalement une fonction doit tenir sur 25 lignes ; aprs il faut songer dcouper la fonction en
plusieurs fonctions.
Q : Ma fonction fait 5 pages, mais elle fonctionne bien. Alors pourquoi la dcouper ?
R : Pensez au jour o, dans quelques annes, vous ou lun de vos collgues devra
modifier ou corriger votre fonction. Il faudra alors relire dans le dtail ces 5 pages de
code Ne vaut-il mieux pas prendre 5 minutes aujourdhui pour dcouper la
fonction ?
Guide de style du Langage C
5
Eviter les fonctions trop larges
Eviter les fonctions trop larges (qui comporte trop de colonnes).
Idalement une fonction doit tenir sur 80 colonnes ; aprs il faut couper les lignes
astucieusement.
Exemple :
ans = (exp(ax)/sqrt(ax)) *
(0.39 +
y * (0.13 +
y * (0.22e-2 +
y * (-0.15 +
y * (0.91 +
y * (-0.20 + y * 0.26) ))))));
plutt que :
ans = (exp(ax)/sqrt(ax))*(0.39+y*(0.13+y*(0.22e-2+
y*(-0.15+y*(0.91+y*(-0.20+y*0.26)))))));
Les prototypes de fonctions avec de nombreux paramtres peuvent tre coups aprs le type
de retour et aprs chaque virgule :
Exemple :
static AP_STRING ApplyFilter(AP_CSTRING term, AP_LPINT termsize, AP_CSTRING
deb, AP_CSTRING filter, int sizein, int sizeout) ;
Peut scrire :
static AP_STRING
ApplyFilter(AP_CSTRING term, AP_LPINT termsize,
AP_CSTRING deb, AP_CSTRING filter,
int sizein, int sizeout) ;
Ou encore mieux, en commentant les paramtres :
static AP_STRING
ApplyFilter(AP_CSTRING term, /* Le terme */
AP_LPINT termsize, /* La taille du terme */
AP_CSTRING deb, /* Debut de ma modif */
AP_CSTRING filter, /* Le filtre */
int sizein, /* taille filtre dentre */
int sizeout) ; /* taille filtre de sortie */
Les chanes de caractres trop longues peuvent facilement tre rparties sur plusieurs lignes.
Exemple :
printf ( "Salut les amis, je suis un gentil programme, "
"bien que certains pensent que je suis un peu bavard") ;
Guide de Style du Langage C
6
Arer le code
Utilisez des espaces et des lignes blanches pour arer le code afin daugmenter la lisibilit :
- Mettre un espace aprs chaque mot-cl
- Ne pas coller les accolades avec le reste du code
- Mettre des espaces autour dune sous-expression pour la dtacher
- Mettre des espaces aprs les virgules
- Mettre des espaces aprs les points virgules au sein dun for
Comparez les deux versions :
float chebev(float a,float b,float c[],int m,float x)
{
float d=0.0,dd=0.0,sv,y,y2;
int j;
if((x-a)*(x-b)>0.0)
return 0.0;
y2=2.0*(y=(2.0*x-a-b)/(b-a));
for(j=m-1;j>=1;j--){
sv=d;
d=y2*d-dd+c[j];
dd=sv;
}
return y*d-dd+0.5*c[0];
}
float chebev(float a, float b, float c[], int m, float x)
{
float d=0.0, dd=0.0, sv, y, y2;
int j;
if ( (x-a)*(x-b) > 0.0)
return 0.0;
y2 = 2.0 * ( y = (2.0*x-a-b)/(b-a) );
for (j=m-1; j>=1; j--) {
sv = d;
d = y2*d dd + c[j];
dd = sv;
}
return y*d dd + 0.5*c[0];
}
Vous pouvez galement insrer des espaces pour aligner des lments semblables de lignes en
lignes. Exemple :
prixHT=prixUnitaire*quantite; /* Calcul du prix HT des articles */
prixTTC=prixHT*(1+tauxTVA); /* Calcul du prix TTC */
est moins lisible que :
Guide de style du Langage C
7
prixHT = prixUnitaire * quantite ; /* Calcul du prix HT des articles * /
prixTTC = prixHT * (1 + tauxTVA) ; /* Calcul du prix TTC */
Allger le code
Souvent, un code concis est plus lisible :
Supprimez les paramtres et accolades inutiles
Profitez des possibilits du langage
Utilisez les rgles de simplifications mathmatiques et logiques
Version Grasse Version Light
return ( <valeur> ) ; return <valeur> ;
if (<test>) {
<une intruction >;
}
if (<test>)
<une intruction >;
a = (b*c) + (d/e); a = b*c + d/e;
if ((a*b) > (c+d)) if (a*b > c+d)
if (a>b)
c=a;
else
c=b;
c= (a>b) ? a : b;
a=i;
i=i+1;
a=i++;
a = a + b; a+=b;
!a && !b !(a || b)
!a || !b !(a && b)
BOOL EstTropGrand(int index)
{
if (index > MAX_INDEX)
return TRUE;
else return FALSE;
}
BOOL EstTropGrand(int index)
{
return index > MAX_INDEX;
}
Evitez toutefois les raccourcis saisissants qui rendent le code abscons . Comme :
z=x && y++ ; /* Grosse astuce */
au lieu de :
if (x==0)
z=0 ;
else
z=y++ ;
Si vous hsitez entre plusieurs types dcriture, choisissez toujours ce qui vous semble le plus
lisible.
Guide de Style du Langage C
8
3 RGLES DE NOMMAGE
De mme que pour lindentation du code C, il ny a pas de rgles absolues concernant le
nommage des identificateurs en C. Encore une fois, le plus important est dadopter un style
unique au sein dun mme groupe de travail et de le respecter.
Nommage en gnral
Les noms peuvent tre en anglais ou en franais condition dviter les mlanges de langue
au sein dune mme application.
Q : Je suis martiniquais et mon le ensoleille est loin ; alors pour me consoler et me
rappeler le pays, jaime bien choisir des identificateurs en crole.
R : Le jour o vous ne serez plus l pour maintenir votre code vos collgues auront la
joie dapprendre le crole.
Les noms choisis doivent tre explicites, ni trop longs, ni trop courts. Lemploi dabrviations
est acceptable pour viter les identificateurs trop longs :
Trop court ou pas clair Trop long Bon
tot totalDesVentesDeLAnnee totalVenteAn
nbf nombreDeFenetresOuvertes nbFenetresOuvertes
AfficheResultat AfficheNombreDeFichiersTrouves AfficheNbFicTrouves
Tri TriClientsParOrdreAlphabetique TriClientsAlpha
VE TYPE_VECTEUR_D_ENTIERS VECTEUR_INT
flag etat_Fichier_Bien_Ouvert ficOuvert
En gnral on choisira les noms longs et explicites pour les identificateurs qui ont une porte
globale tandis que des noms courts sont acceptables pour des variables locales et des
compteurs de boucles.
Evitez :
les noms mal orthographis ou phontiss pour gagner de la place
clientCouran
AficheMoyene
ipotez
davoir des noms similaires pour deux variables diffrentes
numClient,noClient
coord2,coordZ
davoir des chiffres dans les noms
total1, total2, total3
dabrger diffremment les mmes mots
nbFenetresOuvertes, nbreFenetresFermees
ficClients, fcrFournisseurs
Guide de style du Langage C
9
Nommage des fonctions
Les noms de fonctions sont toujours en lettres minuscules mais capitaliss. Cest dire que
les mots lintrieur dun nom commencent par une majuscule.
Par exemple :
Initialiser
DessinerObjet
ChargeFichierClients
CalculeDateDePaques
CreateFunnyButton
On notera que les verbes peuvent tre linfinitif ou limpratif. Toutefois, au sein dune
mme application, on choisira linfinitif ou limpratif, mais sans mlanger les deux.
Pour une fonction effectuant un test, le nom reflte le sens de la proposition :
EstUnJourFerie
EstVisible
IsAnEmptyFile
IsValidWindow
Certains dveloppeurs aiment prfixer les fonctions locales un fichier (dclares en
static) avec un underscore :
_CalculeTotIntermediaire
_CompareNbOfItems
Nommage des macros, des types et des constantes
Les macros, les types et les constantes sont toujours en lettres majuscules. Les mots
lintrieur dun nom sont spars par des underscores.
Exemples :
TAILLE_BUFFER
MAX_LINE_SIZE
MEMORY_HANDLE
WINDOW
Certaines personnes prfrent choisir des noms en minuscules pour les types. Dans ce cas, ils
utilisent le suffixe _t pour indiquer quil sagit dun type.
Exemples :
typedef float coordonnee_t ;
typedef int booleen_t;
Guide de Style du Langage C
10
Nommage des variables, paramtres et champs
Les variables et les paramtres sont en minuscules. Les mots lintrieur dun nom
commencent par des majuscules, mais la premire lettre est toujours une minuscule.
Exemples :
age
adresseClient
numSecuriteSociale
On rencontre parfois aussi des dveloppeurs prfrant nutiliser que des minuscules en
utilisant lunderscore comme sparateur :
adresse_client
num_securite_sociale
Cette pratique allonge les identificateurs sans vraiment les rendre plus lisibles. En tout tat de
cause viter les identificateurs tout en minuscules sans sparateur:
adresseclient
numsecuritesociale
Certains dveloppeurs prfixent les noms avec un code rappelant son type ou son usage. Cest
la notation hongroise , invente par Charles Simonyi. Par exemple i prfixe les entiers,
ch les caractres, c les compteurs, f les flags Le p prfixe tous les pointeurs
et se compose avec le prfixe du type point. Par exemple :
char chDernierLu; /* dernier caractre lu */
int cValeurCourante; /* un compteur */
int *piLongeur /* pointeur sur un entier contenant la longueur */
BOOL *ppfFichierLu /* pointeur sur un pointeur sur un flag
indiquant si le fichier est lu */
Les rgles de nommage sont les mmes pour les noms de champs des struct et union.
Certains dveloppeurs les prfixent toutefois avec m_ (le m tant linitiale de
member ) ou avec un autre prfixe rappelant le nom de la structure.
Exemples :
struct CLIENT {
char *m_nom ;
char *m_prenom ;
int m_age ;
} ;
Guide de style du Langage C
11
typedef struct tagBITMAPINFOHEADER {
DWORD biSize;
LONG biWidth;
LONG biHeight;
WORD biPlanes;
WORD biBitCount;
DWORD biCompression;
DWORD biSizeImage;
LONG biXPelsPerMeter;
LONG biYPelsPerMeter;
DWORD biClrUsed;
DWORD biClrImportant;
} BITMAPINFOHEADER;
Guide de Style du Langage C
12
4 DCLARATIONS
Dclarations des constantes
Ne jamais utiliser de constantes en dur dans le code, mais utilisez #define ou const pour
les dfinir en un endroit unique (gnralement dans un fichier den-tte).
Dclarations des types avec typedef
typedef pour les types standards
Ne pas hsiter construire des types plutt que dutiliser directement les types standards.
Par exemple, pour une temprature, plutt que dutiliser le type float, dfinissez :
typedef float temperature_t ;
Ainsi, si vous vous apercevez que le choix de float tait mauvais (par exemple, pas
assez prcis) et que vous voulez utiliser le type double , il suffit de modifier la
dclaration de type plutt que de retrouver et de modifier toutes les dclarations de variables
et de paramtres correspondant des tempratures.
typedef pour les structures, les unions et les types numrs
Plutt que de dclarer chaque variable de type structure en faisant :
struct stToto *monToto ;
Dfinissez un type pour la structure :
typedef struct stToto TOTO ;
Toutes les dclarations seront plus lisibles :
TOTO *monToto ;
Le mme raisonnement sapplique aux unions et aux types numrs.
typedef pour les pointeurs de fonctions
Lors de manipulation de pointeurs de fonctions, dfinir avec typedef un type pour chaque
pointeur de fonctions. Ceci permet de clarifier la lecture du code.
Par exemple, la fonction systme signal, dfinie dans signal.h peut se dclarer ainsi :
Guide de style du Langage C
13
void (*signal)(int,void(*)(int)))(int) ;
Ou en dclarant un type fonction , ce qui donne :
typedef void (*SIGNAL_HANDLER)(int) ;
SIGNAL_HANDLER signal(int, SIGNAL_HANDLER) ;
Ne jamais utiliser #define la place de typedef
Certaines personnes pensent que typedef nest pas trs utile et quil est tout fait valable
de dclarer un type en utilisant #define. Ainsi, premire vue :
typedef char * STRING ;
et :
#define STRING char *
sont quivalents. Mais imaginons maintenant la dclaration suivante :
STRING str1,str2 ;
Si le type a t dfini avec une macro, la dclaration va sexpanser ainsi :
char *str1,str2 ;
Au lieu de leffet recherch :
char *str1,*str2 ;
Dclarations des fonctions
Avant dtre appele, une fonction doit avoir t dclare :
soit parce quelle a t dfinie plus haut :
double CalculeDelta(double a, double b, double c)
{
return b*b-4*a*c ;
}
int NbreSolutions(double a, double b, double c)
{
double delta=CalculeDelta(a,b,c) ;
if (delta>0.0) return 2 ;
if (delta<0.0) return 0 ;
return 1 ;
}
soit parce quelle a t dclare laide dun prototype
Guide de Style du Langage C
14
double CalculeDelta(double a, double b, double c) ;
int NbreSolutions(double a, double b, double c)
{
double delta=CalculeDelta(a,b,c) ;
if (delta>0.0) return 2 ;
if (delta<0.0) return 0 ;
return 1 ;
}
Lancienne notation :
int NbreSolutions(a, b, c)
double a,b,c;
{
...
}
nexiste plus que pour des raisons de compatibilit ; il faut donc ne plus lutiliser.
Toujours spcifier le type retourn par une fonction. Si une fonction ne retourne rien, utilisez
void :
void AfficheObjet(OBJECT *unObjet) ;
Si vous voulez bien mettre en vidence quune fonction na pas de paramtre, vous pouvez
aussi utiliser void dans la liste des arguments :
DATE *Aujourdhui(void) ;
Si une fonction nest utilise que dans le fichier o elle est dfinie, utilisez static pour la
dclarer.
/* cette fonction est locale au fichier courant */
static int _Square(int x)
{
return x*x ;
}
Eviter quune fonction ait un nombre trs important de paramtres. Dans ce cas, la fois pour
des raisons de lisibilit et defficacit, il est souhaitable de dfinir une structure qui va
regrouper les arguments .
Guide de style du Langage C
15
Dclarations des variables
Dclaration des variables
Dclarez une variable par ligne de code et commentez la variable droite de la dclaration,
sur la mme ligne.
Initialisation des variables
Initialisez toutes les variables, ne pas compter sur le fait quune variable sera initialise par
dfaut zro.
Dclaration des tableaux
Lors de la dclaration dun tableau, nutilisez pas une taille arbitraire, donne en dur. Allouez
le nombre exact dlments dans un tableau sans ajouter quelques lments pour tenter de
minimiser le risque dun dbordement de tableau.
char message[500]; /* mauvais */
strcpy(message, "Bonjour ");
strcat(message, nomUtilisateur) ;
AfficheMessage(message);
Faites plutt :
#define TAILLE_BONJOUR 8
#define MAX_TAILLE_NOM 50
char message[TAILLE_BONJOUR+MAX_TAILLE_NOM+1];
strcpy(message, "Bonjour ");
strcat(message, nomUtilisateur) ;
AfficheMessage(message);
De cette manire, on sait immdiatement comment ajuster la taille dun tableau si ncessaire.
On peut encore rendre le code de lexemple plus robuste en ajoutant des assertions (cf. Les
assertions, page 34)
#define MSG_BONJOUR Bonjour
#define TAILLE_BONJOUR (sizeof(MSG_BONJOUR)-1)
#define MAX_TAILLE_NOM 50
char message[TAILLE_BONJOUR+MAX_TAILLE_NOM+1];
assert(strlen(MSG_BONJOUR)==TAILLE_BONJOUR);
assert(strlen(nomUtilisateur)<=MAX_TAILLE_NOM);
strcpy(message, MSG_BONJOUR);
strcat(message, nomUtilisateur) ;
AfficheMessage (message);
Guide de Style du Langage C
16
Variables globales
Minimisez lutilisation des variables globales. Utiliser static pour dclarer une variable
globale locale un fichier.
Si une fonction utilise une variable globale uniquement pour elle, dfinissez la variable en
static lintrieur de la fonction. Par exemple :
int Compteur()
{
static int valeurCompteur=1 ;
return valeurCompteur++ ;
}
Utilisation dune variable pour deux buts diffrents
Il est parfois tentant dutiliser la mme variable plusieurs fois pour des usages diffrents car
cela vite des dclarations multiples et permet dconomiser un peu de mmoire :
/* Calcule le nombre de pages necessaires pour imprimer
le fichier clients */
nb = NbEnregistrements(clients) ;
nb *=tailleLigne ;
if (nb%taillePage == 0)
nb/=taillePage ;
else
nb=nb/taillePage+1 ;
Dans cet exemple la variable au nom vague de nb change plusieurs fois de signification.
Ce code aurait t plus clair ainsi :
/* Calcule le nombre de pages necessaires pour imprimer
le fichier clients */
nbLignes = NbEnregistrements(clients) ;
totalTaillesLignes = nbLignes * tailleLigne ;
if (totalTaillesLignes%taillePage == 0)
nbPages = totalTaillesLignes / taillePage ;
else
nbPages = totalTaillesLignes / taillePage + 1;
Utilisation de const
Utilisez const pour dclarer un pointeur sur un objet qui ne sera pas modifi. Ceci est
particulirement intressant lorsque lon dfinit les paramtres dune fonction. Par exemple,
considrons le prototype de la fonction standard strcpy qui copie une chane de caractres
dans une autre :
char *strcpy( char *s1, const char *s2 ) ;
Guide de style du Langage C
17
Mme si on a oubli comment fonctionne cette fonction, un simple coup dil au prototype
permet de voir que cest s2 que lon copie dans s1 puisque la chane s2 nest pas modifiable.
De plus imaginons le code suivant :
#include <string.h>
#define MAX_STRING 100
typedef struct tagCLIENT {
char m_nom[MAX_STRING] ;
char m_prenom[MAX_STRING] ;
int m_age ;
} CLIENT;
void ChangeNomClient(CLIENT *unClient,const char *nouveauNom)
{
strcpy(nouveauNom,unClient->m_nom) ; /* bug ! ! ! ! */
}
Le dveloppeur a permut par erreur les arguments du strcpy mais il a eu la sagesse :
de dfinir un prototype pour sa fonction ChangeNomClient qui prcise que le nouveau
nom pass en paramtre ne peut pas tre modifi
de ne pas utiliser strcpy sans que le prototype de la fonction soit connu grce
linclusion du fichier string.h
Ainsi, le compilateur signale une erreur qui aurait t bien plus difficile trouver autrement.
Q : Mon code peut trs bien fonctionner sans const, alors pourquoi se casser la
tte ?
R : Un bon dveloppeur fait tout pour dtecter les problmes potentiels le plus tt
possible (lors de la compilation) et const est lun des outils fournis par C pour y
parvenir. Il vaut mieux passer un peu de temps mettre des consts que de passer
une semaine trouver un bug avec le debugger.
Guide de Style du Langage C
18
5 STRUCTURES DE CONTRLE
Attention aux points-virgules
Un point virgule aprs un if, un while ou un for est considr comme une instruction
vide.
Ainsi :
if (x[i] > big);
big=x[i];
est quivalent :
if (x[i] > big) {};
big=x[i];
et donc quivalent :
big=x[i];
Le else dans les if imbriqus
Considrons le code suivant :
if (x==0)
if (y ==0) error();
else {
z = x + y;
f(&z);
}
Contrairement ce que le dveloppeur a voulu faire, le compilateur rattache le else au
deuxime if et le code, signifie :
if (x==0) {
if (y ==0)
error();
else {
z = x + y;
f(&z);
}
}
En effet, il aurait fallu crire :
Guide de style du Langage C
19
if (x==0) {
if (y ==0) error();
} else {
z = x + y;
f(&z);
}
Les tests monstrueux
Par exemple, supposons une fonction qui dtermine le taux dassurance dune personne en
fonction de sa situation :
typedef enum {Fumeur,NonFumeur} fumeur_t;
typedef enum {Homme,Femme} sexe_t;
typedef enum {Marie,Celibataire} statut_marital_t;
typedef int age_t ;
typedef double taux_t ;
/* Calcule le taux dassurance */
taux_t
CalculeTaux(fumeur_t fume, sexe_t sexe, statut_marital_t statut, age_t age)
{
if (fume==Fumeur) {
if (age<18) return 10.0 ;
else
if (age>60 && sexe==Homme) return 40.0 ;
else {
if (status_marital==Celibataire) return 20.0 ;
else {
if (age<40) . . .
etc etc etc
Notre fonction CalculeTaux fait 10 pages et on espre que tous les cas sont bien traits
(comment en tre certain ?). Le jour o tout les taux changent, une bonne journe de travail
est ncessaire pour mettre la fonction jour.
Ce test aurait pu tre remplac par une table :
typedef enum {Fumeur,NonFumeur} fumeur_t;
#define NB_ETATS_FUMEURS 2
typedef enum {Homme,Femme} sexe_t;
#define NB_SEXES 2
typedef enum {Marie,Celibataire} statut_marital_t;
#define NB_STATUTS 2
typedef int age_t ;
typedef enum {enfant=8,ado=18,jeune=39,quadra=59,papy} classe_age_t;
#define NB_CLASSE_AGE 5
typedef double taux_t ;
Guide de Style du Langage C
20
taux_t TableDesTaux[NB_ETATS_FUMEURS][NB_SEXES][NB_STATUTS][NB_CLASSE_AGE];
/* Charge la table des taux depuis un fichier */
int ChargeTableDesTaux()
{

}
/* Determine la classe dage */
classe_age_t DetermineClasseAge(age_t age)
{
if (age<=enfant) return enfant;
if (age<=ado) return ado;
if (age<=jeune) return jeune;
if (age<=quadra) return quadra;
return papy;
}
/* Calcule le taux dassurance */
taux_t
CalculeTaux(fumeur_t fume, sexe_t sexe, statut_marital_t statut, age_t age)
{
return TableDesTaux[fume][sexe][statut][DetermineClasseAge(age)];
}
Avec cette version, le calcul est simple et trs efficace et de plus tous les taux sont stocks
dans un fichier qui pourra tre dit lorsque les taux changeront sans avoir modifier le code.
Le switch
Toujours avoir une clause default dans un switch, mme si on suppose que tout les
cas possibles sont traits. Au pire, utiliser assert (cf. Les assertions, page 34) :
switch (codeSexe) {
case M :
printf("Masculin);
break;
case F :
printf("Feminin);
break;
default:
assert(0); /* Cas Impossible */
}
Loubli dun break dans une clause case conduit lexcution du code de la clause
suivante :
Guide de style du Langage C
21
switch (codeSexe) {
case M :
printf("Masculin);
case F :
printf("Feminin);
default:
assert(0); /* Cas Impossible */
}
Par exemple, lexcution de ce code avec codeSexe gal M afficherait :
MasculinFeminin
assertion failed : 0, file toto.c, line 153
Si on veut omettre le break intentionnellement, il est souhaitable de mettre un commentaire
pour montrer quil ne sagit pas dun oubli :
switch (codeSexe) {
case F :
printf("Mes hommages, Madame.\n);
/* On continue... */
case M :
/* traitement commun homme/femme*/
printf("Comment-allez vous ?\n);
default:
assert(0); /* Cas Impossible */
}
Il est possible de dclarer des variables lintrieur dun bloc switch et mme de mettre des
instructions avant le premier case :
switch (expr) {
int i=4;
f(i);
case 0:
i=17;
/* On continue... */
default:
printf(%d\n,i);
}
Mais le code avant le premier case nest jamais excut. Par consquent, dans lexemple, la
variable i nest pas initialise 4 et la fonction f nest pas appele.
La boucle infinie
Pour une boucle infinie, utilisez for( ; ;), plus lgant que while(1)
Guide de Style du Langage C
22
Les modifications intempestives des indices de boucles
Il faut viter le modifier les compteurs de boucles lintrieur des boucles et galement viter
dutiliser la valeur dun compteur en sortie de boucles.
Exemple :
for(i=0;i<NB_CLIENTS;i++) {
if (<condition derreur>) {
i=NB_CLIENTS+1; /* pour sortir de la boucle */
} else {
/* traitement */

}
}
if (i==NB_CLIENTS+1) /* pour traiter lerreur */
Dans ce cas, il aurait t plus clair davoir :
erreurSurClient=FALSE;
for(i=0;i<NB_CLIENTS;i++) {
if (<condition derreur>) {
erreurSurClient=TRUE;
break;
}
/* traitement */

}
if (erreurSurClient) /* pour traiter lerreur */
Utilisation du goto
Utilisez le goto avec parcimonie. Beaucoup de dveloppeurs sont des adversaires rsolus du
goto pour des raisons idologiques alors que dans certains cas, lutilisation du goto permet
davoir un code nettement plus concis et lisible. Le cas typique est lutilisation du goto pour
sortir de plusieurs structures de contrle trs imbriques lorsquune erreur a t dtecte.
Par exemple :
Guide de style du Langage C
23
int UnCalcul()
{
int i,j,k ;
int errorcode =0;
char *buffer ;
buffer=(char *) malloc(MY_BUF_SIZE) ;
if (buffer==NULL)
return 1 ;
for(i=0 ;i<MAXHEIGHT ;i++) {
for(j=0 ;j<MAXWIDTH ;j++) {
for(k=0 ;k<MAXSAMPLE ;k++) {
if (<dtection derreur >) {
errorcode=-2 ;
goto error ;
}


}

}

}
error :
/* on libe`re le buffer, me^me en cas derreur */
free(buffer) ;
return errorcode ;
}
est plus clair (et plus efficace !) que :
Guide de Style du Langage C
24
int UnCalcul()
{
int i,j,k ;
int errorcode =0;
char *buffer ;
buffer=(char *) malloc(MY_BUF_SIZE) ;
if (buffer==NULL)
errorcode=-1;
else {
for(i=0 ;i<MAXHEIGHT && !errorcode;i++) {
for(j=0 ;j<MAXWIDTH && !errorcode;j++) {
for(k=0 ;k<MAXSAMPLE && !errorcode;k++) {
if (<dtection derreur >) {
errorcode=-2 ;
} else {

}
}
if (!errorcode) {

}
}
if (!errorcode) {
..
}
}
free(buffer) ;
}
return errorcode ;
}
Lorsque cest possible, on prfrera utiliser continue ou break. Par exemple :
for ( i = 0; i < LENGTH; i++ ) {
for ( j = 0; j < WIDTH; j++) {
if ( lines[i][j] == \0 )
goto stoploop ;
else lengths[i] = j;
}
stoploop : ;
}
est avantageusement replac par
for ( i = 0; i < LENGTH; i++ ) {
for ( j = 0; j < WIDTH; j++) {
if ( lines[i][j] == \0 )
break ;
else lengths[i] = j;
}
}
Toutes les techniques exposes ici :
utilisation du goto
utilisation de break ou continue
return anticip (un return qui nest pas la dernire instruction dune fonction)
Guide de style du Langage C
25
sont en contradiction avec les principes de la programmation structure et il faut donc
toujours les utiliser avec prcaution et uniquement lorsquelles permettent un gain substantiel
concernant la lisibilit du code.
Les goto inutiles
Mme si le goto est parfois utile, il faut proscrire les utilisations du type :
goto onyva ;
while (expression) {
/* traitement 1 */

onyva :
/* traitement 2 */

}
On peut obtenir le mme rsultat avec un code plus lisible et sans goto :
for(;;) {
/* traitement 2 */

if (!expression) break;
/* traitement 1 */

}
Autre exemple abominable :
if (fileOpened) {
if(dataReady) {
nbPage++;
goto goprint ;
}
else goto errorData;
} else {
OpenTheFile() ;
PrepareData () ;
goprint :
/* gros traitement */

}
return OK;
errorData:
return ERROR;
Ce type dcriture qui tente de rutiliser un bout de code grce un goto est inacceptable.
Typiquement, il aurait t ncessaire de crer une fonction :
Guide de Style du Langage C
26
void PrintData()
{
/* notre gros traitement */

}
Afin de pouvoir rutiliser le traitement sans faire appel un goto. Le code modifi serait
alors :
if (fileOpened) {
if(dataReady) {
nbPage++;
PrintData();
}
else return ERROR;
} else {
OpenTheFile() ;
prepareData () ;
PrintData();
}
return OK;
Guide de style du Langage C
27
6 FICHIERS DEN-TTE (INCLUDES)
Factoriser les dfinitions
Ne jamais dupliquer une information (constante, macro) dans plusieurs fichiers .c. Utilisez
les fichiers den-tte .h
Pas de chemins absolus
Dans un #include, ne pas utiliser des noms de chemins absolus du type :
#include "d:\MonProjet\Sources\Foo\toto.h"
Car si vous dplacez vos fichiers, il faudra modifier vos sources.
Utilisez pour ceci loption I du compilateur.
Guillemets ou <> ?
Utilisez #include "" pour les en-ttes de lapplication et #include <> pour les en-
ttes des librairies, cela acclrera la recherche des fichiers en-ttes lors de la compilation.
Utiliser les prototypes dfinis dans les includes
Ajoutez les #include correspondant aux fonctions utilises afin de bnficier des
prototypes qui y sont dfinis.
Prvenir la double inclusion et prvoir C++
Les fichiers den-tte doivent avoir un mcanisme prvenant la double inclusion et prvoyant
lutilisation de ces fonctions depuis le C++.
Exemple pour un fichier foo.h :
Guide de Style du Langage C
28
#if !defined ( _FOO_H )
#define _FOO_H
#if defined( __cplusplus )
extern "C" {
#endif
/* les dfinitions */
int FOO_ComputeValue(int source);
int FOO_ConvertValue(int value,int factor);
/* Fin des definitions */
#if defined( __cplusplus )
}
#endif
#endif /* !defined( _FOO_H ) */
Des includes autonomes
Un fichier include doit se suffire lui-mme en faisant lui-mme les includes ncessaires
son fonctionnement. En effet lutilisateur dun include ne doit pas devoir faire dautres
includes pralables pour obtenir des dfinitions ncessaires quil na pas connatre.
Q : Oui, mais ne risque-t-on pas dinclure plusieurs fois le mme fichier si les fichiers
den-tte font des inclusions que lon ne matrise pas ?
R : Cest pour cette raison quil faut un mcanisme pour prvenir la double inclusion
dans les fichiers den-tte.
Guide de style du Langage C
29
7 GESTION DES ERREURS EN C
Dtection et traitement des erreurs
Un bon programme doit tre capable de grer toutes les erreurs sans planter. Au pire, en cas
de problme grave (plus de mmoire disponible, par exemple) le programme doit
sinterrompre aprs avoir affich un message indiquant la cause de larrt.
Il faut en particulier, lorsque lon appelle une fonction, tester au retour quune erreur na pas
eu lieu.
Exemple :
/* Copie un fichier en supprimant les lignes commenant par # */
void SuppressComments(const char *filenameIn, const char *filenameOut)
{
#define MAX_LINE_SIZE 132
FILE *streamIn, *streamOut;
char *line;
streamIn=fopen(filenameIn);
streamOut=fopen(filenameOut);
line=(char *)malloc(MAX_LINE_SIZE+1);
while(fgets(line, MAX_LINE_SIZE, streamIn)) {
if (line[0]!=#)
fprintf(streamOut,%s,line);
}
free(line);
fclose(streamIn);
fclose(streamOut);
}
Ds quune erreur va se produire, le comportement de la fonction sera imprvisible car rien
nest fait pour grer les erreurs. Pourtant, les causes derreurs sont ici multiples :
- Erreur douverture du fichier dentre filenameIn
- Erreur douverture du fichier de sortie filenameOut
- Erreur dallocation du buffer line
- Erreur lors de la lecture du fichier dentre
- Erreur lors de lcriture du fichier de sortie
- Erreur lors de la fermeture du fichier dentre
- Erreur lors de la fermeture du fichier de sortie
Il faut donc dtecter et traiter au mieux ces erreurs. De plus notre fonction va tre appele par
dautres fonctions et elle doit elle-mme reporter les erreurs quelle a dtectes. On a donc :
Guide de Style du Langage C
30
int SuppressComments(const char *filenameIn, const char *filenameOut)
{
#define MAX_LINE_SIZE 132
FILE *streamIn, *streamOut;
char *line;
streamIn=fopen(filenameIn);
if (streamIn==NULL) return 1;
streamOut=fopen(filenameOut);
if (streamOut==NULL) {
fclose(streamIn);
return 2;
}
line=(char *)malloc(MAX_LINE_SIZE+1);
if (line==NULL) {
fclose(streamIn);
fclose(streamOut);
return 3;
}
while(fgets(line, MAX_LINE_SIZE, streamIn)) {
if (line[0]!=#) {
if (fprintf(streamOut,%s,line)<0) {
free(line);
fclose(streamIn);
fclose(streamOut);
return 4;
}
}
}
free(line);
if (ferror(streamIn)) {
fclose(streamIn);
fclose(streamOut);
return 5;
}
if (fclose(streamIn)) {
fclose(streamOut);
return 6;
}
if (fclose(streamOut))
return 7;
return 0;
}
Notre fonction est maintenant robuste mais considrablement alourdie cause de la ncessit
de librer les ressources avant de sortir. De plus, il est facile doublier de librer une ressource
dans un cas particulier. Lutilisation de goto peut nous permettre de centraliser la libration
des ressources la fin de la fonction. De plus, cette fonction retourne des codes derreurs sous
forme de nombre ngatifs (une tradition en C) mais il serait souhaitable davoir un vrai type
pour les codes derreur sous la forme dun type numr :
typedef enum {
ERROR_OK = 0 ,
ERROR_CANT_OPEN_FILE_FOR_WRITING = -1,
ERROR_CANT_OPEN_FILE_FOR_READING = -2,
etc etc
} ERROR_CODE ;
Guide de style du Langage C
31
ERROR_CODE
SuppressComments(const char *filenameIn, const char *filenameOut)
{
#define MAX_LINE_SIZE 132
FILE *streamIn=NULL, *streamOut=NULL;
char *line=NULL;
ERROR_CODE code=ERROR_OK;
streamIn=fopen(filenameIn,r);
if (streamIn==NULL) {
code=ERROR_CANT_OPEN_FILE_FOR_READING;
goto error;
}
streamOut=fopen(filenameOut,w);
if (streamOut==NULL) {
code=ERROR_CANT_OPEN_FILE_FOR_WRITING;
goto error;
}
line=(char *)malloc(MAX_LINE_SIZE+1);
if (line==NULL) {
code=ERROR_MEMORY;
goto error;
}
while(fgets(line, MAX_LINE_SIZE, streamIn)) {
if (line[0]!=#) {
if (fprintf(streamOut,%s,line)<0) {
code=ERROR_WRITING;
goto error;
}
}
}
if (ferror(streamIn)) code=ERROR_READING;
error:
if (line) free(line);
if (streamIn) {
if (fclose(streamIn)) {
fclose(streamOut);
if (code==ERROR_OK) code=ERROR_CLOSING;
}
}
if (streamOut) {
if (fclose(streamOut)) {
if (code==ERROR_OK) code=ERROR_CLOSING;
}
}
return code;
}
La fonction renvoie maintenant des codes derreurs exploitables et la libration des ressources
est la mme, que lon sorte naturellement de la fonction ou quune erreur ait t dtecte. Il
est clair que le code est plus lourd que sans traitement derreurs, mais cest le prix payer. Par
exemple, si la fonction retourne lerreur ERROR_WRITING lapplication pourra exploiter ce
code pour afficher :
Erreur lors de lcriture du fichier xxxx .
Vrifiez que votre disque nest pas satur.
Alors que sans traitement derreur le comportement serait :
Guide de Style du Langage C
32
- plantage pur et simple de lapplication ou
- lapplication na pas trait le fichier mais ne signale rien
Q : Quand une fonction retourne dj une valeur qui nest pas un entier positif,
comment peut-elle retourner un code derreur ?
R : Deux techniques sont possibles :
1- ajouter un paramtre qui est un pointeur sur un code derreur
Exemple :
float
CalculeVitesse(int temps,float vitesseInitiale,ERROR_CODE *erreur) ;
2- avoir une variable globale dans laquelle on stocke le code derreur
Cest la solution retenue par la librairie C avec la variable globale errno
Utilisation de errno
La librairie C standard dfini le symbole errno qui permet de consulter un code derreur
affect par les fonctions de la librairie.
Au dmarrage de lapplication, la valeur de errno est 0, mais aucun mcanisme ne permet
de remettre errno 0 entre chaque appel. Il est donc souhaitable dinitialiser errno 0 avant
lappel de la fonction pour laquelle on gre les erreurs. Exemple :
#include <errno.h>
...
errno=0 ;
f=fopen(toto,r);
La valeur de errno peut tre modifie par la librairie quil y ait ou quil ny ait pas eu
derreur. Par consquent, ne pas faire :
errno=0 ;
f=fopen(toto,r);
if (errno) {
/* traitement de lerreur */
car errno peut tre non nul aprs lappel fopen mme si fopen a fonctionn
correctement. Il faut faire :
errno=0 ;
f=fopen(toto,r);
if (f==NULL) { /* echec de louverture */
/* consultation du code derreur */
switch (errno) {
case ...
}
Le problme cest que la norme ISO ne dfini que deux codes derreurs :
EDOM (argument mathmatique illgal) et ERANGE (dpassement de capacit). Tous les
autres codes derreur dpendent de limplmentation. Lutilisation de errno implique donc des
problmes de portabilit.
Guide de style du Langage C
33
Q : Je rcupre un code derreur errno en cas derreur, mais jaimerais afficher un
message derreur plus sympathique que Erreur n-142
R : Les fonctions standards perror et strerror permettent dafficher ou
dobtenir un message derreur partir dun code errno. Toutefois, ces messages
sont la plupart du temps en anglais.
Cast des appels de fonctions en void
Lorsque quun dveloppeur choisi dignorer dlibrment la valeur retourne par un appel de
fonction, et donc de ne pas grer une erreur ventuelle, il est prfrable de clairement
lexprimer dans le code ainsi :
(void) fclose(f) ;
Dailleurs, certains compilateurs ou certains outils de vrification de code comme lint
signalent systmatiquement toutes les valeurs de retour non exploites. Dans ce cas, il peut
tre souhaitable dcrire :
(void) printf(Salut la Foule !\n) ;
(void) strcpy(s1,s2) ;
mme si printf est en pratique peu susceptible de retourner une erreur et strcpy
incapable de le faire.
Guide de Style du Langage C
34
8 STRATGIES POUR AIDER LA MISE AU POINT
Les assertions
Lors de lexcution, un programme C ne vrifie rien quant la validit des oprations
effectues. Par exemple :
/* met une lettre dune chai^ne en majuscule */
void CapitalizeLetter(char *str,int index)
{
str[index]=toupper(str[index]) ;
}
void Foo()
{
char *toto[]= "Hello" ;
CapitalizeLetter(toto,10) ; /* bug ! ! */
}
Lors de lexcution de la fonction CapitalizeLetter appele depuis la fonction Foo, le
programme modifiera le 11
ime
lment dune chane de caractres ne comportant que 5
lettres. Au mieux le programme va planter, mais sans indiquer la raison du plantage qui ne
risque dtre dcouverte quaprs une longue sance sous debugger. Au pire le comportement
de programme va tre alatoire et le problme sera encore plus difficile rsoudre.
Cest donc le devoir mais aussi lintrt du dveloppeur dcrire du code qui va vrifier, dans
la mesure du possible, que le code est valide. Le langage C fourni un outil pour ceci : le
fichier en-tte assert.h dfini une macro de nom assert qui vrifie quune condition que
lon suppose vraie est bien vrifie :
#include <assert.h>
/* met une lettre dune chai^ne en majuscule */
void CapitalizeLetter(char *str,int index)
{
assert(index >=0);
assert(index<strlen(str));
str[index]=toupper(str[index]) ;
assert(!islower(str[index]));
}
Avec ce nouveau code plus robuste, lors de lexcution de la fonction Foo, lexcution
sinterrompra aprs que le programme ait affich un message de diagnostique du type:
assertion failed : index<strlen(str), file foo.c, line 12
Q : Toutes vrifications avec assert, a va ralentir mon code ! Ne vais-je pas
perdre tout le bnfice de lefficacit du langage C ?
Guide de style du Langage C
35
R : assert nest pas une fonction mais une macro qui, lorsque lon compile la
version finale que lon va livrer lutilisateur, sexpanse en rien afin que les tests ne
soient plus effectus
Lorsque lon dveloppe, on gnre un excutable dit de mise au point (debug) qui nest
pas optimis et qui comporte tous les lments aidant la mise au point. Dans ce cas la
dfinition de assert, qui est une macro est du type :
#define assert(exp) (void)((exp) || (_assert(#exp, __FILE__, __LINE__), 0))
avec _assert qui est une fonction affichant le message de diagnostique et sortant du
programme.
Lorsque lon livre lapplication au client, on gnre un excutable dit de livraison
(release) qui lui est optimis et ne comporte pas les lments aidant la mise au point. Dans
ce cas la dfinition de assert est :
#define assert(exp) ((void)0)
ce qui fait que dans la version de livraison, les tests ne sont plus effectus et ne pnalisent
donc pas les performances .
Q : assert est gnial, maintenant je gre toutes mes erreurs avec a. Par exemple :
FILE *f=fopen("toto", "r") ;
assert(f !=NULL) ;
R : assert ne sert pas grer les erreurs mais crire du code de vrification lors
de la phase de mise au point. Dans la version de livraison, les asserts ne sont plus
pris en compte et dans cet exemple, on ne vrifierait plus que le fichier se soit
correctement ouvert.
Q : La dfinition dassert me semble bien complexe ! Pourquoi ne pas crire
#define assert(exp) if(exp) _assert(#exp, __FILE__, __LINE__)
R : voir bug avec les macros (3) page 41
Utiliser les warnings
Configurer le compilateur pour obtenir le maximum de message de warning. Les warnings
signalent toujours des problmes potentiels quil faut analyser.
Q : Tous ces messages de warning mnervent en mempchent de travailler
correctement. Alors jai configur le compilateur pour quil naffiche plus les
warnings. Malin, non ?
R : Hrtique ! Comme toujours, il faut dtecter les problmes potentiels le plus tt
possible et les warnings sont des outils prcieux dont on ne peut se priver
impunment. Il faut au contraire configurer le compilateur pour obtenir davantage
de message de warning.
Q : Jai ce code qui me donnait un warning propos dun problme de conversion :
double b ;

Guide de Style du Langage C


36
int a=2*sqrt(b) ;
Heureusement, je nai pas perdu de temps et jai fait :
int a=2*(int) sqrt(b) ;
Jai plus de warning ! Suis-je fort ?
R : Le but du jeu nest pas de trouver le moyen le plus rapide de ne plus voir le
message. Dans cet exemple, la bonne correction aurait sans doute t :
int a=(int) (2*sqrt(b)+0.5) ;
Un warning doit inciter rflchir !.
Libration des ressources
Toujours librer les ressources alloues :
La mmoire alloue par malloc doit toujours tre libre par free
Un fichier ouvert par fopen doit tre ferm par fclose

Loubli dune libration nest malheureusement pas immdiatement visible puisque le
programme fonctionne bien (tant quil reste des ressources disponibles, bien sur). Toutefois,
ceci une rpercussion sur lefficacit du programme mais peut aussi dgrader les
performances des autres applications fonctionnant en parallle.
Q : Jai fait un programme qui alloue des ressources (mmoire dynamique), fait un
traitement et sort juste derrire. Nest ce pas inutile de se fatiguer librer les
ressources alors que le systme les libre de lui-mme la sortie du programme ?
R : Rien ne garantit que ces ressources seront effectivement libres par le systme.
Cest a priori vrai sous Unix mais compltement faux avec Window 3.x et pas
toujours vrai avec Windows 95 et NT.
Utilisation doutils de vrification
Il existe des outils comme Purify, BoundChecker ou CodeGuard qui permettent de vrifier au
cours dune excution que lapplication na pas effectu dopration illgale, que toutes les
ressources ont t correctement libres et que les fonctions systmes ont t correctement
utilises. Il est clair que ces outils sont une aide prcieuse lors de la mise au point dune
application.
Guide de style du Langage C
37
9 PROBLMES DE PORTABILIT
NULL
Ne pas supposer que NULL est cod avec (void *) 0. Par consquent, dans les tests ne faites
pas :
if ( !(f=fopen("foo.txt", "r") ) {
/* Erreur douverture du fichier */
}
mais :
if ((f=fopen("foo.txt", "r") == NULL) {
/* Erreur douverture du fichier */
}
Oprateurs de dcalage
Nutiliser les dcalages binaires (oprateurs >> << >>= et <<=) que sur des entiers non
signs. Le comportement sur des entiers signs est imprvisible et dpend des machines et des
compilateurs.
Commentaires la C++
Les commentaires la C++ du type :
// Un commentaire
ne font pas parti de la norme bien quils soient accepts par certains compilateurs. Il vaut
mieux donc sen tenir aux commentaires C traditionnels.
Taille des types entiers
Le C possde trois types dentiers : short, int et long. La norme prcise que le type
short prend au moins 16 bits et que le type long prend au moins 32 bits. Quant au type
int, on peut juste supposer quil est possible daffecter un short un int sans effectuer
de troncation. Un programme portable doit donc supposer quun int peut ne faire que 16
bits. Un programme peut utiliser les constantes dfinies dans le fichier en-tte limits.h
pour connatre les valeurs minimales et maximales quun type donn peut contenir.
Guide de Style du Langage C
38
Ordre des octets lintrieur dun entier
Dans un entier cod sur plusieurs octets, rien ne suppose que les octets les plus significatifs
seront cods en premier ou en dernier et cela dpend des architectures. Par exemple :
unsigned short mot=0x80;
/* recuperation de loctet de poid faible et de loctet de poid fort*/
poidFaible = *( (unsigned char *) &mot) ;
poidFort = *(((unsigned char *) &mot)+1) ;
nest absolument pas portable. La bonne version est :
poidFaible = mot&0xff
poidFort = (mot>>8) & 0xff;
Prfrez les fonctions standards
Lorsque cest possible utilisez toujours les fonctions standards de la librairie.
Les fonctions d'entre/sortie de bas niveau la Unix peuvent couramment tre remplaces
par des appels de haut niveau qui sont normaliss.
Appel de bas niveau (propritaire) Appel de haut niveau (standard ISO)
open fopen
create fopen
close fclose
eof feof
read fread
write fwrite
tell ftell
lseek fseek
dup freopen
dup2 freopen
Si la buffrisation induite par les fonctions de haut niveau est gnante, elle peut tre
supprime par un appel la fonction setvbuf.
Certaines fonctions oprant sur les blocs mmoires qui existent sur certains systmes Unix
doivent tre abandonnes au profit des fonctions dfinies par la norme. Voici une table
d'quivalence:
Propritaire Standard ISO
bcopy(src,dest,len) memcpy(dest,src,len)
bzero(dest,len) memset(dest,val,len)
bcmp(s1,s2,len) memcmp(s1,s2,len)
De plus vitez le code du type :
Guide de style du Langage C
39
if (car>=a && car<=z) car=car+A-a;
Qui repose sur les proprits du code ASCII. Les fonctions de la librairie font la mme chose
de manire portable :
if (islower(car)) car=toupper(car) ;
Alignements dans les structures
Lorsque le compilateur implmente une structure en mmoire, les diffrents membres de la
structure ne sont pas forcment contigus en mmoire. Ainsi dans la structure :
struct FOO {
char ch ;
int i ;
} ;
limplmentation en mmoire peut tre (par exemple):
ch Inutilis Inutilis Inutilis i (bits 0-7) i (bits 8-15) i (bits 16-23) i (bits 24-31)
En gnral, le compilateur possde des options permettant de modifier la faon dont les
membres des structures sont aligns en mmoire.
La macro offsetof(type, membre) permet de connatre quelle adresse mmoire
relative un membre donn est implment. Dans notre exemple, offsetof(FOO,i) vaut 4.
C permet de dfinir des champs de bits lintrieur dune structure. Par exemple :
struct {
unsigned int color : 4;
unsigned int underline : 1;
unsigned int italic: 1;
unsigned int bold : 1;
} screen[25][80];
Lordre dimplmentation des bits lintrieur dun mot mmoire dpend de limplmentation
et rien ne permet de le dterminer.
Guide de Style du Langage C
40
10 BUGS CLASSIQUES
Les macros (1)
Lors de la dclaration dune macro, Pensez aux parenthses autour des paramtres
Par exemple, veut dfinir une macro qui multiplie par 2 et que lon crit :
#define MULPAR2(x) 2*x
lexpression
r=MULPAR2(a) ;
Donne bien
r=2*a ;
Mais
r=MULPAR2(a+b) ;
Donne
r=2*a+b ; /* Argh !*/
Il fallait dfinir :
#define MULPAR2(x) 2*(x)
le rsultat aurait alors t :
r=2*(a+b); /* Correct !*/
Maintenant considrons la macro suivante :
#define PLUS10(x) 10+(x)
On a bien mis les parenthses autour du paramtre mais si on a :
r=2*PLUS10(a);
Cela va tre expans ainsi :
r=2*10+(a) ; /* Encore faux ! ! */
Il fallait dfinir :
#define PLUS10(x) (10+(x))
Guide de style du Langage C
41
Pour avoir :
r=2*(10+(a)) ; /* Cette fois cest juste! */
Les macros (2)
Lorsque lon utilise plusieurs fois le mme paramtre dans la dfinition dune macro, il faut
tre trs mfiant. Par exemple, avec :
#define max(a,b) (((a)>(b)) ? (a) : (b))
Lexpression :
r=max(2*a+foo(b),exp(c)+log(d)) ;
est expans en :
r=(((2*a+foo(b))>(exp(c)+log(d)) ? (2*a+foo(b)) : (exp(c)+log(d))) ;
Par consquent, un des deux paramtres sera calcul deux fois, ce qui nest pas trs efficace.
De plus, si deux appels conscutifs de la fonction foo avec la mme valeur ne rendent pas
le mme rsultat, le deuxime calcul du premier paramtre de lexemple peut renvoyer une
valeur diffrente du premier calcul !
Les macros (3)
Une macro nest pas une instruction. En effet, considrons la macro suivante :
#define PRINTIFPOSITIVE(x) if ((x)>=0) printf("%d est > 0\n ",(x))
Le code :
PRINTIFPOSITIVE(a+b) ;
fonctionne parfaitement, part le fait que lexpression a+b est calcule deux fois lorsquelle
est positive. Mais le code :
if (z)
PRINTIFPOSITIVE(a+b) ;
else
z=0;
est quivalent :
if (z)
if ((a+b)>=0) printf(%d est > 0\n,(a+b));
else
z=0;
Guide de Style du Langage C
42
quivalent lui-mme :
if (z) {
if ((a+b)>=0)
printf(%d est > 0\n,(a+b));
else
z=0;
}
La seule dfinition correcte de cette macro nest malheureusement pas trs lisible :
#define PRINTIFPOSITIVE(x) (void) ((x) >= 0 && printf("%d est > 0\n ",(x)))
La vrit est ailleurs
Le langage C ne possde pas de type boolen, alors on dfini souvent :
typedef int BOOL;
#define FALSE 0
#define TRUE 1
Le problme avec ces dfinitions, cest que tout autre valeur que 0, et pas seulement 1 est
considre comme vraie . Par consquent, ne jamais faire un test du type :
if (FichierOuvert() == TRUE) {
Naturellement, il suffisait dcrire :
if (FichierOuvert()) {
= nest pas ==
if (x = y) {.
Ne compare pas x et y mais affecte x avec y.
Dans le cas o vous voudriez vraiment faire une affectation, privilgiez lcriture
if ((x = y) != 0) {
Plus claire.
De mme, attention aux confusions entre & (et binaire) et && (et logique) ainsi quentre | (ou
binaire) et || (ou logique).
Certaines personnes, lors dune comparaison avec une constante prfre crire :
if (0 == x)
au lieu de
Guide de style du Langage C
43
if (x == 0)
car, en cas doubli du deuxime =, on aurait
if (0 = x)
Ce qui provoquerait une erreur de compilation. Toutefois, cette astuce nuit quelque peu la
lisibilit du code.
Commentaire mal ferm
Considrons le code suivant :
prixHT = prixUnitaire * quantite ; /* Calcul du prix HT des articles * /
prixTTC = prixHT * (1 + tauxTVA) ; /* Calcul du prix TTC */
A la suite dune faute de frappe, un espace sest insr entre le * et le / de la fin du
commentaire sur la premire ligne. Le commentaire de la premire ligne commence au
premier /* et se termine au prochain */ qui est en fait la fin du deuxime
commentaire. Le compilateur considre donc que la deuxime ligne de code est commente !
C est gourmand
On dit que C est gourmand (greedy) car il recherche le mot-cl le plus grand possible lors de
la compilation. Ainsi lexpression :
i+++j
est interprt comme :
i++ + j
et non comme :
i+ ++j
Q : Comment est interprt x+++++y ?
R : Comme x++ ++ + y ce qui provoque une erreur de syntaxe puisque x++ ++
est illgal. Linterprtation lgale x++ + ++y nest pas considre par le
compilateur .
Cette caractristique peut causer des bugs particulirement vicieux, comme le pige de la
division par une valeur pointe :
Dans :
y= x/*p ;
Guide de Style du Langage C
44
Le compilateur interprte /* comme un dbut de commentaire. Utilisez des espaces ou des
paramtres pour rsoudre le problme :
y= x / *p ;
y= x/(*p) ;
Dans lexemple prcdent, on peut supposer que lerreur soit dtecte au moment de la
compilation. Mais pas toujours, car imaginons maintenant que lon ait :
if (x/*p /* si la division de x par la valeur pointe par p */
> facteur) /* est superieure au facteur */
Le commentaire commence aprs le x et fini au prochain */ , donc le compilateur
comprendra :
if (x>facteur)
a[i] = i++; ne fonctionne pas !
Dans lexpression
a[i] = i++;
La sous expression i++ provoque un effet de bord qui modifie la valeur de i. Le standard C
spcifie que le comportement de cette expression est indfini. Le problme serait similaire
pour :
a[i++] = i;
En gnral, on crit ce type dexpression parce que lon a utilis une boucle while la place
dune boucle for. Ainsi, plutt que :
i=0 ;
while ( i<n )
y[i++]=x[i] ; /* bug */
Il faut crire :
for(i=0 ;i<n ;i++)
y[i]=x[i] ;
Ordre dvaluation des sous expressions
Dans une expression, lordre dvaluation des sous expressions nest pas garanti. Ainsi, le
rsultat de :
( i++ + 1 ) * ( 2 + i )
est indfini. Seuls quatre oprations garantissent lordre dvaluation : && , || , le
couple ? : et loprateur de continuation , .
Guide de style du Langage C
45
Ainsi, il est parfaitement lgal dcrire :
if (b !=0 && a/b>limite)
car a/b ne sera valu que si b est bien diffrent de 0 et il ny a donc pas de risque derreur
cause par une division par 0.
Ordre dvaluation des paramtres
De mme que pour les sous expressions, lordre dvaluation des paramtres nest pas dfini
lors de lappel dune fonction. Par consquent le comportement de :
add( i + 1, i = j + 2 );
est indfini.
int a = 1000, b = 1000; long c = a * b; ne fonctionne pas !
Dans lexpression :
int a = 1000, b = 1000; long c = a * b;
a et b sont des entiers int et leur produit est toujours de type int . Le produit est ensuite
promu en type long pour affecter c. Si la valeur 1000*1000 dpasse la capacit du type
int, le rsultat escompt sera faux.
Pour avoir le bon rsultat, il faut convertir un des deux arguments avant la multiplication
laide dun cast :
long c= (long) a *b ;
Grer les dpassements de capacit
C a deux types darithmtique entire : signe et non signe. Il nexiste pas de notion de
dpassement pour larithmtique non sign car tous les calculs sont effectus modulo 2
n
, n
tant le nombre de bits du type rsultat. Si lun des oprandes est sign et lautre non sign,
loprande sign est converti en non sign et il ny a donc toujours pas de dpassement
possible.
Par contre un dpassement peut survenir lors dune opration dont les deux arguments sont
signs. Le rsultat dun dpassement est indfini et il est sain de ne rien supposer concernant
ce rsultat. En C, rien ne permet de savoir si un dpassement a eu lieu. Donc, si un
dpassement est possible il faut tester cette possibilit avant deffectuer lopration. Par
exemple, si
Guide de Style du Langage C
46
c= a + b ;
risque de provoquer un dpassement, on peut sen assurer ainsi :
if (a > INT_MAX b) {
/* on gre le depassement */
} else
c = a+b ;
NB : INT_MAX est lentier le plus grand reprsentable avec le type int. Cette constante est
dfinie dans le fichier den-tte limits.h.
Ne pas confondre *p++ et (*p)++
*p++
Incrmente le pointeur p et retourne la valeur pointe par p avant lincrmentation. Alors que :
(*p)++
Incrmente la valeur pointe par p
Tableau de pointeurs ou pointeur de tableaux ?
int *pointeurs[10];
est un tableau de 10 pointeurs sur des entiers, tandis que :
int (*pointeur)[10];
est un pointeur sur un tableau de 10 entiers
Pointeur constant ou constante pointe ?
const char *p
ou
char const *p
dclarent un pointeur sur un caractre constant. (on ne peut pas modifier le caractre point
mais on peut modifier le pointeur)
Tandis que :
char * const p
Dclare un pointeur constant sur un caractre (on peut modifier le caractre point mais pas la
valeur du pointeur)
Guide de style du Langage C
47
const char * const p
ou
char const * const p
dclare un pointeur constant sur un caractre constant (on ne peut ni modifier la valeur du
pointeur ni la valeur du caractre point)
Distinction pointeurs/tableaux
On a coutume de dire que les tableaux et les pointeurs sont quivalents en C. Il existe
quelques diffrences, en particulier :
char s[]= abc;
dfini un tableau de 4 caractres et les initialise avec la chane abc . Tandis que :
char *p= abc;
Dfini un pointeur qui pointe sur un tableau de 4 caractres contenant la chane abc . Dans
ce dernier cas, le systme nassure pas que le tableau est modifiable ou quil soit lusage du
seul pointeur p. Par consquent, le comportement de :
*p= a ;
est indfini.
Prcision du calcul flottant
Le calcul flottant se fait avec une certaine prcision et ne reflte pas toujours certaines vrits
mathmatiques. Par exemple :
void foo (double a)
{
double i ;
double step=a/100 ;
for(i=a ; i!=0.0 ;i-=step) {

}
}
Trs probablement, la boucle ne sarrtera jamais car les calculs ne seront pas assez prcis : la
variable i va passer par une valeur proche de 0 mais ne sera jamais nulle.
Gnralement, cause de ces problmes de prcision, on utilise rarement les oprateurs ==
ou != en calcul flottant, mais on fait des comparaisons du type :
if(fabs(a - b) <= epsilon * a)
Avec une valeur habilement choisie pour epsilon.
Guide de Style du Langage C
48
Fonction retournant une valeur alloue sur la pile
Considrons une fonction qui permet de convertir un prix stock sous forme de valeur
flottante en une chane de caractres justifie droite :
typedef float prix_t ;
char *PrixVersChaine(prix_t prix)
{
char chainePrix[11];
sprintf(chainePrix,%10.2f,prix);
return chainePrix; /* bug */
}
Cette fonction retourne un buffer qui est une variable locale alloue sur la pile. A la sortie de
la fonction, lespace mmoire du buffer est donc libr et le pointeur retourn par notre
fonction est invalide. On devrait avoir :
char *PrixVersChaine(prix_t prix)
{
static char chainePrix[11];
sprintf(chainePrix,%10.2f,prix);
return chainePrix;
}
Dans cette version le buffer est allou en mmoire globale grce lemploi du mot cl
static pour la dclaration. La valeur retourne par la fonction est maintenant valide, mais
comme le buffer a t allou en mmoire globale, son contenu sera cras au prochain appel
de la fonction. La fonction, de ce fait, nest pas rentrante, ce qui serait particulirement
gnant dans le cas dune application multi-thread. La meilleure solution tant donc dallouer
le buffer en mmoire dynamique avec malloc :
char *PrixVersChaine(prix_t prix)
{
char *chainePrix=(char *) malloc(11);
if (chainePrix==NULL) return NULL;
sprintf(chainePrix,%10.2f,prix);
return chainePrix;
}
Dans ce cas la fonction appelante devra librer la chane de caractres retourne avec un appel
la fonction free lorsque la chane deviendra inutile .
Notre fonction a encore un petit problme On alloue 11 octets dans le buffer car avec la
chane de formatage du printf ( %10.2 ), on sait que la sortie devra faire 10 caractres
(donc 11 avec le \0 terminal). Outre le fait quil est toujours dsagrable davoir des
constantes en dur, il existe un cas pour lequel la sortie va excder les 10 caractres : printf
ne fait jamais de troncation et si le prix dpasse 9999999 (sept 9 car 3 caractres sont pris
Guide de style du Langage C
49
par le point dcimal et les centimes), la sortie sera plus large que 10 caractres et il y aura un
dbordement dans le buffer chainePrix. Voici une version robuste :
#define LARGEUR_CHAINE_PRIX 10
char *PrixVersChaine(prix_t prix)
{
/* nombre doctets necessaires pour afficher un prix */
int nbOctetsNecessaires;
char *chainePrix;
/* calcul nombre doctets necessaires pour afficher le prix
log base 10 du prix + 1 pour les unites +
1 pour le point decimal + 2 pour les centimes */
nbOctetsNecessaires=((int) log10(prix)) + 4;
/* On essaie de formater droite sur LARGEUR_CHAINE_PRIX
caracte`res sinon, on deborde */
nbOctetsNecessaires=max(nbOctetsNecessaires, LARGEUR_CHAINE_PRIX);
chainePrix=(char *) malloc(nbOctetsNecessaires+1);
if (chainePrix==NULL) return NULL;
sprintf(chainePrix,%*.2f,LARGEUR_CHAINE_PRIX,prix);
return chainePrix;
}
Guide de Style du Langage C
50
11 OPTIMISATIONS
Optimisation vs Lisibilit
C est un langage trs efficace mais on cherche toujours optimiser son programme pour avoir
les meilleurs temps de calcul. Malheureusement les optimisations nuisent souvent la
lisibilit du code ; il faut donc bien savoir ce qui est important pour votre projet, lefficacit
ou la lisibilit. Il faut valuer si une optimisation vaut le prix pay en terme de perte de
maintenabilit du programme et essayer den minimiser le cot laide de commentaires.
Cibler les optimisations
Lexprience montre que toutes les fonctions ne sont pas gales en terme de temps
dexcution. On constate en gnral que 20% des fonctions consomment 80% du temps
dexcution. Il ne sert donc rien de tout optimiser sans discernement mais il faut dterminer
quelles sont les fonctions sur lesquelles les efforts devront porter. Des outils (profilers)
permettent de calculer le temps pass dans chaque fonction lors dune excution.
Valider les optimisations
On doit toujours tre capable de savoir ce quapporte chaque optimisation. Pour ceci il suffit
de chronomtrer la portion de code avant et aprs loptimisation. Les exemples ne sont pas
rares o certaines optimisations ne font rien gagner ou mme font perdre de lefficacit.
En effet les compilateurs modernes optimisent votre code parfois mieux que vous-mme.
Quelques techniques
Sortir les calculs des boucles
Au sein dune boucle, il ne faut pas calculer la mme chose chaque itration. Si une
expression est constante, il faut la calculer avant la boucle. Par exemple :
for(i=0;i<nbventes;i++)
prix[i]=prix[i]*remise*(1+tauxtva);
devient :
facteurRemiseTVA=remise*(1+tauxtva);
for(i=0;i<nbventes;i++)
prix[i]*= facteurRemiseTVA;
Guide de style du Langage C
51
Inversion boucle/test
Si un test au sein dune boucle se fait sur une expression qui toujours fausse ou vraie pour
toutes les itrations, il est souhaitable de tester lexpression avant de boucler. Par exemple :
for(i=0 ;i<nbOperations; i++) {
if(operation==Credit)
montant+=operation[i];
else
montant-=operation[i];
}
soptimise en
if (operation==Credit)
for(i=0 ;i<nbOperations; i++) montant+=operation[i];
else
for(i=0 ;i<nbOperations; i++) montant-=operation[i];
Optimisation de la condition de sortie de boucle
Pour chaque itration dans une boucle, la condition de sortie de la boucle est teste. Il est donc
souhaitable doptimiser ce test.
Par exemple, si le processeur de la machine compare plus rapidement une valeur avec 0
quavec une variable :
for(i=0 ;i<nbOperations; i++)
montant+=operation[i];
soptimise en :
for(i=NbOperations-1 ;i>=0; i--)
montant+=operation[i];
Fusion de boucle
Deux boucles conscutives avec les mmes bornes peuvent parfois tre fusionnes en une
seule boucle :
for(i=0;i<nbOperations;i++)
operation[i]*=tauxTVA;
for(i=0;i<nbOperations;i++)
libelleOperation[i]=strdup(Taxe);
soptimise en :
Guide de Style du Langage C
52
for(i=0;i<nbOperations;i++) {
operation[i]*=tauxTVA;
libelleOperation[i]=strdup(Taxe);
}
Droulage de boucles (1)
Pour les boucles ou le nombre ditrations est faible et constant, il est possible de supprimer la
boucle. Par exemple :
for(somme=0,i=0;i<8;i++)
somme+=operation[i];
soptimise en:
somme=operation[0] + operation[1] + operation[2] + operation[3] +
operation[4] + operation[5] + operation[6] + operation[7];
Droulage de boucles (2)
Afin de minimiser le cot de gestion dune boucle (test de la condition darrt, incrmentation
du compteur), on peut transformer la boucle de manire diviser le nombre ditrations par
deux, comme dans cet exemple :
for(somme=0,i=0;i<nboperations;i++)
somme+=operation[i];
soptimise en:
somme=0;
nboperations--;
i=0;
while (i<nboperations) {
/* deux operations par iterations !! */
somme+=operation[i++];
somme+=operation[i++];
}
/* traite dernie`re valeur si nboperations impair */
if (i != (++nboperations))
somme+=operation[i];
Q : Dans cet exemple, on fait deux oprations par itration, mais pourquoi par 3, 4
ou mme plus
R : Cest effectivement possible, mais cela fait un code de plus en plus volumineux
Les amateurs de bizarrerie pourront examiner une technique exotique doptimisation par
droulage de boucle : le dispositif de Duff (cf . page 65).
Inversion de boucles imbriques
Pour deux boucles imbriques, il faut essayer davoir la boucle la plus active lintrieur .
Exemple:
Guide de style du Langage C
53
#define MAT_W 2000
#define MAT_H 10
double matrix[MAT_H][MAT_W];
for(somme=0.0,i=0;i<MAT_W;i++) /* 2000 boucles */
for(j=0;j<MAT_H;j++) /* seulement 10 boucles */
somme+=j*matrix[j][i];
Dans cet exemple, on a 2000 itrations pour la boucle externe et 2000x10=20000 itrations
pour la boucle interne soit un total de 22000 itrations. On peut optimiser ainsi :
for(somme=0.0,i=0;i<MAT_H;i++) /* les 10 boucles a` lexterieur */
for(j=0;j<MAT_W;j++) /* les 2000 a` linterieur */
somme+=i*matrix[i][j];
Ainsi, on a 10 itrations pour la boucle externes et 10x2000=20000 itrations pour la boucle
interne, soit un total de 20010 itrations. On a donc conomis 22000-20010=1990 itrations.
Tester en premier les cas les plus courants
Lors dune succession de tests, il est souhaitable de commencer tester les cas les plus
courants afin de minimiser, en moyenne, le nombre de tests effectus. Par exemple :
if (letter==!) {

} else
if (letter==?) {

} else
if (isdigit(letter)) {

} else
if (isupper(letter)) {

} else {

}
devient:
if (isupper(letter)) {

} else
if (isdigit(letter)) {

} else {
if (letter==!) {

} else
if (letter==?) {

} else {

}
Guide de Style du Langage C
54
Tester en premier les cas les moins coteux tester
Lorsque dans une expression boolenne lordre dvaluation est a priori indiffrent, il est
possible de profiter du fait que lordre dvaluation est connu pour tester en premier les sous
conditions qui sont calcules le plus rapidement. Par exemple :
if (a > 0.0 || exp(b)>log(c*d+a) )
est plus efficace que :
if (exp(b)>log(c*d+a) || a > 0.0)
Utiliser un switch la place dun if
Un switch est toujours plus rapide quune succession de if, mme lorsque le nombre de
case est important. Par exemple :
if (isupper(letter)) {

} else
if (isdigit(letter)) {

} else {
if (letter==!) {

} else
if (letter==?) {

} else {

}
devient :
Guide de style du Langage C
55
switch (letter) {
case ! :

break ;
/* les chiffres */
case 0 : case 1 : case 2 : case 3 : case 4 :
case 5 : case 6 : case 7 : case 8 : case 9 :

break;
case ? :

break ;
/* les lettres majuscules */
case A : case B : case C : case D : case E :
case F : case G : case H : case I : case J :
case K : case L : case M : case N : case O :
case P : case Q : case R : case S : case T :
case U : case V : case W : case X : case Y :
case Z :

break;
default:

}
Utilisation des pointeurs pour accder aux tableaux
Il est parfois plus efficace dutiliser des pointeurs pour parcourir un tableau. Par exemple :
#define MAT_W 1000
#define MAT_H 1000
double matrix[MAT_H][MAT_W];
for(somme=0.0,i=0;i<MAT_W;i++)
for(j=0;j<MAT_H;j++)
somme+=matrix[j][i];
la boucle devient :
double *p =matrix ;
for(somme=0.0,i=MAT_W*MAT_H;i;i--,p++)
somme+=*p;
Cest en particulier souvent intressant avec les traitements de chanes de caractres :
/* Met une chai^ne en majuscules */
void MettreEnMajuscules(char *s)
{
int i;
for(i=0;i<strlen(s);i++)
s[i]=toupper(s[i]);
}
Guide de Style du Langage C
56
Dans cet exemple, on calcule la longueur de la chane chaque itration. Evidemment, la
premire optimisation qui vient lesprit est (cf. Optimisation de la condition de sortie de
boucle, page 51):
/* Met une chai^ne en majuscules */
void MettreEnMajuscules(char *s)
{
int i;
for(i=strlen(s)-1; i>=0; i--)
s[i]=toupper(s[i]);
}
Mais on peut optimiser davantage en utilisant les pointeurs. Pour calculer la longueur dune
chane, la fonction strlen utilise une boucle qui parcoure la chane jusqu trouver le 0
terminal. La fonction MettreEnMajuscule possde aussi une boucle qui parcoure la
chane, ici pour mettre les caractres en majuscules. Il est donc facile dutiliser les pointeurs
pour, avec une boucle unique, mettre les caractres en majuscules jusqu rencontrer le 0
terminal :
void MettreEnMajuscules(char *s)
{
for(;*s;s++)
*s=toupper(*s);
}
Prcalcul dans des tables
Lorsque quune fonction calcule une valeur en utilisant un algorithme coteux, et que les
paramtres de la fonction ne peuvent prendre quun nombre raisonnable de combinaisons de
valeur distinctes, on peut calculer une bonne fois pour toutes tous les rsultats possibles de la
fonction et stocker ces valeurs dans une table. A lexcution, il suffira daccder la table
pour retourner le rsultat. Par exemple :
/* inversion des bits dans un octets */
typedef unsigned char BYTE;
BYTE InvertByte(BYTE in)
{
BYTE out=0,maskIn=0x80,maskOut=0x01;
int i;
for(i=0;i<8;i++) {
if (in&maskIn) out|=maskOut;
maskIn >>=1;
maskOut<<=1;
}
return out;
}
Devient:
Guide de style du Langage C
57
/* On a calcule toutes les possibilites (256 au total) */
static const BYTE _Permutations[256] = {
0x00, 0x80, 0x40, 0xc0, 0x20, 0xa0, 0x60, 0xe0,
0x10, 0x90, 0x50, 0xd0, 0x30, 0xb0, 0x70, 0xf0,
0x08, 0x88, 0x48, 0xc8, 0x28, 0xa8, 0x68, 0xe8,
0x18, 0x98, 0x58, 0xd8, 0x38, 0xb8, 0x78, 0xf8,
0x04, 0x84, 0x44, 0xc4, 0x24, 0xa4, 0x64, 0xe4,
0x14, 0x94, 0x54, 0xd4, 0x34, 0xb4, 0x74, 0xf4,
0x0c, 0x8c, 0x4c, 0xcc, 0x2c, 0xac, 0x6c, 0xec,
0x1c, 0x9c, 0x5c, 0xdc, 0x3c, 0xbc, 0x7c, 0xfc,
0x02, 0x82, 0x42, 0xc2, 0x22, 0xa2, 0x62, 0xe2,
0x12, 0x92, 0x52, 0xd2, 0x32, 0xb2, 0x72, 0xf2,
0x0a, 0x8a, 0x4a, 0xca, 0x2a, 0xaa, 0x6a, 0xea,
0x1a, 0x9a, 0x5a, 0xda, 0x3a, 0xba, 0x7a, 0xfa,
0x06, 0x86, 0x46, 0xc6, 0x26, 0xa6, 0x66, 0xe6,
0x16, 0x96, 0x56, 0xd6, 0x36, 0xb6, 0x76, 0xf6,
0x0e, 0x8e, 0x4e, 0xce, 0x2e, 0xae, 0x6e, 0xee,
0x1e, 0x9e, 0x5e, 0xde, 0x3e, 0xbe, 0x7e, 0xfe,
0x01, 0x81, 0x41, 0xc1, 0x21, 0xa1, 0x61, 0xe1,
0x11, 0x91, 0x51, 0xd1, 0x31, 0xb1, 0x71, 0xf1,
0x09, 0x89, 0x49, 0xc9, 0x29, 0xa9, 0x69, 0xe9,
0x19, 0x99, 0x59, 0xd9, 0x39, 0xb9, 0x79, 0xf9,
0x05, 0x85, 0x45, 0xc5, 0x25, 0xa5, 0x65, 0xe5,
0x15, 0x95, 0x55, 0xd5, 0x35, 0xb5, 0x75, 0xf5,
0x0d, 0x8d, 0x4d, 0xcd, 0x2d, 0xad, 0x6d, 0xed,
0x1d, 0x9d, 0x5d, 0xdd, 0x3d, 0xbd, 0x7d, 0xfd,
0x03, 0x83, 0x43, 0xc3, 0x23, 0xa3, 0x63, 0xe3,
0x13, 0x93, 0x53, 0xd3, 0x33, 0xb3, 0x73, 0xf3,
0x0b, 0x8b, 0x4b, 0xcb, 0x2b, 0xab, 0x6b, 0xeb,
0x1b, 0x9b, 0x5b, 0xdb, 0x3b, 0xbb, 0x7b, 0xfb,
0x07, 0x87, 0x47, 0xc7, 0x27, 0xa7, 0x67, 0xe7,
0x17, 0x97, 0x57, 0xd7, 0x37, 0xb7, 0x77, 0xf7,
0x0f, 0x8f, 0x4f, 0xcf, 0x2f, 0xaf, 0x6f, 0xef,
0x1f, 0x9f, 0x5f, 0xdf, 0x3f, 0xbf, 0x7f, 0xff
};
BYTE InvertByte(BYTE in)
{
return _Permutations[in];
}
Utilisation de memset, memcpy et memmove avec les tableaux
Une initialisation de tableau ou de structure peut tre efficacement ralise avec memset.
Exemple :
int tableau[] ;
int i ;
for(i=0 ;i<nbElements ;i++)
tableau[i]=0 ;
la boucle devient :
memset(tableau,0,nbElements*sizeof(int)) ;
Guide de Style du Langage C
58
De mme memcpy et memmove peuvent tre employs dans les recopies de tableaux la
place de boucles for.
Enlever les register
Il est possible en C de dclarer une variable en utilisant le mot-cl register qui suggre au
compilateur dimplanter la variable dans un registre du processeur afin daller plus vite (les
oprations avec les registres tant plus rapides que celles avec la mmoire).
Les compilateurs modernes utilisent pleinement les registres du processeur sans quil soit
ncessaire dutiliser register. Comme le nombre de registres est limit, le choix
dimplanter telle ou telle variable dans un registre est assez difficile pour le dveloppeur
tandis que le compilateur fait gnralement le meilleur choix.
Les compilateurs, face une dclaration en register peuvent avoir deux types de
comportement :
le compilateur sait quil est plus malin que le dveloppeur : il ignore totalement le
register et fait lui-mme son choix des variables allouer dans des registres.
le compilateur est servile et honore la dclaration en register tout en sachant quil
aurait fait un meilleur usage des registres.
Donc le mot-cl register ne sert au mieux rien, au pire gnrer du code moins efficace.
Conclusion : il ne faut pas lutiliser.
Guide de style du Langage C
59
12 RALISATION DE COMPOSANTS LOGICIELS
Quest ce quun composant logiciel ?
Un composant logiciel regroupe un ensemble de fonctionnalits utilisables par une ou
plusieurs applications. L'accs ces fonctionnalits se fait au travers d'une interface de
programmation (API).
Les librairies, statiques ou dynamiques, sont des exemples de composant logiciel.
Nommage des fonctions et types exports par une librairie
Afin dviter les problmes de collision entre les noms de fonctions et de types provenant de
librairies diverses et portant le mme nom, il est simple de prfixer tous les symboles exports
par une librairie avec un prfixe unique et reprsentatif. Le prfixe conseill est un trigramme
en lettres majuscules suivi dun underscore . Par exemple, pour une librairie de nom
Soleil , on pourra avoir les fonctions exportes SOL_OpenMyFile,
SOL_ComputeDistance, SOL_FreeResource et les types SOL_PLANET et
SOL_COMET.
Le principe de la "bote noire"
Lorsque qu'un composant est utilis par plusieurs applications, une modification de l'interface
de programmation du composant entrane gnralement une modification de toutes les
applications utilisant ce composant. Ce travail tant donc trs lourd, tout doit donc tre mis en
uvre pour qu'une modification d'un composant n'entrane pas de modification de son
interface de programmation et donc pas de modification des applications.
Pour ceci, l'interface de programmation doit tre conue de manire tre la plus
indpendante possible des structures de donnes et des algorithmes mis en uvre pour raliser
les fonctionnalits du composant. En d'autres termes, les choix d'implmentation doivent
tre "cachs" pour les utilisateurs du composant ; il s'agit du "principe de la bote noire".
Une interface de programmation peut contenir des fonctions, des procdures, des types et
structure de donnes mis la disposition des applications qui vont utiliser le composant. Une
des techniques couramment utilise pour respecter le principe de la "bote noire" est dexclure
toute structure de donnes de l'interface de programmation. Dans ce cas, les donnes
gres par le composant sont toujours manipules au travers d'identifiants ("handlers" ou
pointeurs). L'accs aux proprits des donnes se fait alors uniquement l'aide de fonctions
de l'interface de programmation. L'avantage est immdiat: toute modification des structures de
donnes n'entrane que des modifications l'intrieur du code du composant. De plus, si le
composant est un fichier binaire indpendant comme dans le cas d'une librairie dynamique,
une recompilation des applications n'est pas ncessaire.
Guide de Style du Langage C
60
Comment cacher les structures de donnes
Afin dviter que les utilisateurs dune librairie accdent directement au champ dune
structure, les utilisateurs ne doivent pas disposer de la dfinition de la structure. Mais
comment les utilisateurs peuvent-il manipuler une structure quils ne connaissent pas ?
Pour ceci, les utilisateurs doivent toujours manipuler les donnes au travers de pointeurs, les
structures tant alors alloues en mmoire dynamique par la librairie.
Par exemple, si on a une librairie manipulant des structures de type SOL_PLANET , le
fichier include sol.h contiendra :
#if !defined ( _SOL_H )
#define _SOL_H
#if defined( __cplusplus )
extern "C" {
#endif
/* on declare le type mais pas le contenu de la structure ! !*/
typedef struct tagSOL_PLANET SOL_PLANET ;
/* Fonction pour creer une plane`te */
SOL_PLANET *SOL_CreatePlanet(const char *planetName,int size);
/* Fonction pour liberer une plane`te */
void SOL_FreePlanet(SOL_PLANET *aPlanet);
/* Fonction pour afficher une plane`te */
void SOL_DisplayPlanet(SOL_PLANET *aPlanet,int x,int y);
#if defined( __cplusplus )
}
#endif
#endif /* !defined( _SOL_H ) */
Dans le fichier sol.c on implmente la structure et les fonctions :
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include "sol.h "
/* Definition pour les plane`tes */
struct tagSOL_PLANET {
char *m_Name ; /* Nom de la plane`te */
int m_Size ; /* Diame`tre de la plane`te en pixels */
} ;
/* Puis on implmente les fonctions.*/
Guide de style du Langage C
61
/* Fonction pour creer une plane`te */
SOL_PLANET *SOL_CreatePlanet(const char *planetName,int size)
{
SOL_PLANET *planet;
assert(planetName!=NULL);
assert(size>0);
/* Allocation de la structure */
planet = (SOL_PLANET *)malloc(sizeof(SOL_PLANET));
/* Test Erreur allocation */
if (planet==NULL) return NULL;
/* Copie de la chai^ne pour le nom et affecte le champ */
planet->m_Name = strdup(planetName);
/* Test Erreur allocation */
if (planet->m_Name ==NULL) {
free(planet);
return NULL;
}
/* affecte la taille */
planet->m_Size=size;
/* retourne lobjet */
return planet;
}
/* Fonction pour liberer une plane`te */
void SOL_FreePlanet(SOL_PLANET *aPlanet)
{
assert(aPlanet!=NULL);
assert(aPlanet->m_Name!=NULL);
/* libe`re le nom */
free(aPlanet->m_Name);
/* libe`re la structure */
free(aPlanet);
}
/* Fonction pour afficher une plane`te */
void SOL_DisplayPlanet(SOL_PLANET *aPlanet,int x,int y)
{
/* du code */
}
Les utilisateurs de la librairie ne disposent que de sol.h et de la version compile de la librairie
(sol.so par exemple). Ils peuvent donc crer, dtruire et manipuler des plantes sans avoir
en connatre la dfinition. Le principe de la bote noire est ici scrupuleusement respect.
Htrognit des runtimes C entre les modules
Chaque module (librairie dynamique ou excutable) possde son propre Runtime C qui gre
toutes les fonctions C comme printf, malloc, qsort... Le runtime d'un module n'est
souvent pas le mme que le runtime d'un autre module, en particulier lorsque lon utilise des
compilateurs diffrents ou mme seulement des options de compilation diffrentes. Ceci peut
poser les problmes suivants:
Guide de Style du Langage C
62
Implmentation diffrentes des structures
Une structure systme comme FILE peut tre implmente diffremment d'un runtime
l'autre. Par exemple, le compilateur Microsoft utilise la dfinition suivante:
struct _iobuf {
char *_ptr;
int _cnt;
char *_base;
int _flag;
int _file;
int _charbuf;
int _bufsiz;
char *_tmpfname;
};
typedef struct _iobuf FILE;
Alors que le compilateur WATCOM utilise cette dfinition:
typedef struct __iobuf {
unsigned char *_ptr;
int _cnt;
unsigned char *_base;
unsigned _flag;
int _handle;
unsigned _bufsize;
unsigned char _ungotten;
unsigned char _tmpfchar;
} FILE;
Par consquent, un FILE * obtenu dans un module ne peut tre utilis que par ce module.
Par exemple, l'utilisation par un fprintf dans une librairie dynamique d'un FILE *
obtenu par un fopen appel dans un excutable ne fonctionnera pas et risque de causer un
plantage.
En ce qui concerne les structures dfinies par la norme ISO, ce problme peut ce retrouver
lorsque l'on manipule les structures suivantes:
Guide de style du Langage C
63
Structure Fichier en-tte Fonctions concernes
FILE stdio.h fopen, fclose, fprintf, fscanf, fflush,
freopen, setbuf, setvbuf, tmpfile,
vfprintf, fgetc, fputc, fgets, fputs,
fread, fwrite, fgetpos, fseek, ftell,
rewind, clearerr, feof, ferror
lconv locale.h setlocale
jmp_buf setjmp.h setjmp longjmp
fpos_t stdio.h fgetpos, fsetpos
tm time.h asctime, gmtime, localtime, mktime,
strftime
L'allocateur mmoire
Chaque runtime possde son propre allocateur mmoire grant les appels aux fonctions
malloc, realloc, calloc et free. Par consquent, un bloc mmoire allou dans un
module doit toujours tre rallou et/ou libr par ce mme module.
Par exemple, imaginons qu'une fonction dans une librairie dynamique retourne un objet dans
un buffer mmoire allou par malloc.
/* retourne une copie de la chane s en convertissant
les lettres en majuscules. Retourne NULL en cas d'erreur */
char *ToUppercase(const char *s)
{
char *res,*s2;
/* allocation de la nouvelle chane */
res=s2=(char *) malloc(strlen(s)+1);
/* test si assez de mmoire */
if (res==NULL) return NULL;
/* copie des caractres avec conversion */
while ((*s2++=toupper(*s++))!='\0');
return res;
}
Si dans un excutable on fait:
char *s;
s=ToUppercase("Bonjour");
printf(s);
free(s);
Le programme va afficher BONJOUR et se planter dans l'appel free, puisque le bloc
mmoire appartient la librairie et pas l'excutable.
Guide de Style du Langage C
64
Le futur des composants logiciels
Aujourdhui, lessentiel des composants logiciels sont des librairies, statiques et dynamiques.
Depuis le dbut des annes 1990, un nouveau type de composant a merg, il sagit de lobjet
distribu. Deux standards existent :
DCOM (Distributed Component Object Model) promu par Microsoft et qui fait parti des
technologies ActiveX et OLE,
CORBA (Common Object Request Broker Architecture) dfini par lObject Management
Group (OMG), un consortium de plus de 700 socits.
Un objet distribu est un composant qui encapsule des donnes et des traitements. Il est
accessible via une ou plusieurs API qui sont uniquement composes de fonctions (on
retrouve le principe de la bote noire). Par rapport une librairie, les avantages sont
multiples :
Lorsquun objet t implment sur une machine, toutes les applications fonctionnant
sur cette machine ou sur toutes les autres machines connectes au mme rseau peuvent
utiliser lobjet.
Les objets peuvent possder plusieurs interfaces logicielles (API) correspondant
plusieurs versions du mme objet. Ce mcanisme permet de faire voluer un objet sans
quil soit obligatoire de modifier des applications utilisant une ancienne version de lobjet.
Les objets peuvent interagir mme sils sont distribus sur des machines htrognes
Lorsquune application utilise un objet, cet objet peut tre situ sur une autre machine ou
rsider sur la mme machine sans quil soit ncessaire de modifier lapplication. Il ny
plus de programmation spcifique pour une application devant fonctionner en rseau
Il est possible de distribuer les objets de manire utiliser au mieux lensemble des
ressources informatiques du rseau. Ainsi un objet ncessitant une grande puissance de
calcul pourra tre distribu sur un calculateur tandis quun objet manipulant des donnes
de taille importante sera distribu sur un serveur ayant un grand espace de stockage.
On peut donc parier que les librairies dveloppes aujourdhui seront rapidement transformes
en composants objets. Respecter ds maintenant le principe de la bote noire ne pourra que
faciliter cette migration.
Guide de style du Langage C
65
13 CURIOSITS DU C
a[b] quivalent b[a]
En C, les pointeurs ont des affinits avec les tableaux. En effet :
a[b]
est quivalent :
*(a + b)
Ce qui, grce la commutativit de laddition, est quivalent :
*(b + a)
lui-mme quivalent :
b[a]
Par consquent,
1["abc"]
est parfaitement valide en C et vaut b
Le dispositif de Duff (Duffs device)
M. Duff, dveloppeur chez Lucas Film, a invent une technique doptimisation, qui est une
variante des techniques de droulage de boucles (cf. page 52). Voici un exemple de routine de
copie de zone mmoire avec cette technique :
/* Copie de count octets de from vers to */
void DuffMemcpy(char *to, const char *from, int count)
{
n = (count + 7) / 8;
switch (count % 8) {
case 0: do { *to++ = *from++;
case 7: *to++ = *from++;
case 6: *to++ = *from++;
case 5: *to++ = *from++;
case 4: *to++ = *from++;
case 3: *to++ = *from++;
case 2: *to++ = *from++;
case 1: *to++ = *from++;
} while (--n > 0);
}
Guide de Style du Langage C
66
Cette technique rsout le problme du traitement des octets restants quand count nest
pas multiple de 8 en intercalant une boucle do lintrieur dun switch/case. Ceci est
possible car en C, le switch est une sorte de goto dguis avec les cases jouant le rle de
labels. Inutile de dire que ce type de construction est une ngation totale des principes de la
programmation structure et ne doit tre utilis quen cas de gain significatif sur le temps de
calcul.
Les squences Trigraph
Il sagit dune caractristique peu connu de la norme C, mais toutes les squences suivantes
dans un source sont substitues la compilation par un caractre correspondant :
Squence trigraph Caractre quivalent
??= #
??( [
??/ \
??) ]
?? ^
??< {
??! |
??> }
??- ~
Ceci permet la saisie de code C sur des machines ne possdant pas ces caractres.
Par consquent :
printf( "Comment??!\n" );
Affiche Comment| car ??! est une squence trigraph quivalente |.
Q : Dans ce cas, comment afficher Comment ? ? ! ?
R : printf( "Comment?\?!\n" );
Guide de style du Langage C
67
14 BIBLIOGRAPHIE
The C Complete reference, 2
nd
edition, Hervert Schildt, Ed. MacGraw-Hill, 1990
C Traps and Pitfalls, Andrew Koening, Ed. Addison-Wesley, 1988.
La Qualit en C++, Philippe Prados, Ed. Eyrolles, 1996
C Programming FAQs: Frequently Asked Questions, Steve Summit, Addison-Wesley, 1995
Du code et des hommes, Steve Maguire, Microsoft Press, 1994
Programmation Professionnelle, Steve McConnell, Microsoft Press, 1993
Langages de Programmation C, norme NF EN 29899, ISO/CEI 9899, 1994

Anda mungkin juga menyukai