Poly C Mobile
Poly C Mobile
AU LANGAGE
C
char rahc
[ ]
=
"\n/"
,
redivider
[ ]
=
"Able was I ere I saw elbA"
C
,
*
deliver,reviled
=
1+1
,
niam ; main
( )
{/*\}
\*/
int tni
=
0x0
,
rahctup,putchar
( )
,LACEDx0 = 0xDECAL,
rof ; for
(;(int) (tni);)
(int) (tni)
= reviled ; deliver =
redivider
;
for ((int)(tni)++,++reviled;reviled* *deliver;deliver++,++(int)(tni)) rof
=
(int) -1- (tni)
;reviled--;--deliver;
(tni) = (int)
- 0xDECAL + LACEDx0 -
rof ; for
(reviled--,(int)--(tni);(int) (tni);(int)--(tni),--deliver)
rahctup = putchar
(reviled* *deliver)
;
rahctup * putchar
((char) * (rahc))
;
/*\
{\*/}
Bernard Cassagne
2
3
Bernard Cassagne
Introduction au langage C
norme iso / ansi
4
Laboratoire clips
Université Joseph Fourier & cnrs
Grenoble
Copyright 1997, 1998, 2003 Bernard Cassagne
Copyright 2012 Matthieu Moy
Ce texte est copyrighté et n’est pas dans le domaine public. Sa reproduction
est cependant autorisée à condition de respecter les conditions suivantes :
Toute reproduction sortant du cadre précisé ci-dessus est interdite sans accord
préalable de l’auteur.
Une page permettant d’obtenir ce document sous divers formats est ac-
cessible par l’url :
http://www-verimag.imag.fr/~moy/cours/poly-c/
i
ii
Contents
1 Les bases 5
1.1 Les versions du langage C . . . . . . . . . . . . . . . . . . . . 5
1.2 Langage et bibliothèque standard . . . . . . . . . . . . . . . . 6
1.3 Les phases de compilation . . . . . . . . . . . . . . . . . . . . 6
1.4 Les jeux de caractères . . . . . . . . . . . . . . . . . . . . . . 7
1.5 Les unités lexicales . . . . . . . . . . . . . . . . . . . . . . . . 7
1.5.1 Les mots-clés . . . . . . . . . . . . . . . . . . . . . . . 7
1.5.2 Les identificateurs . . . . . . . . . . . . . . . . . . . . . 8
1.6 Les commentaires . . . . . . . . . . . . . . . . . . . . . . . . . 9
1.7 Les types de base . . . . . . . . . . . . . . . . . . . . . . . . . 12
1.7.1 Les caractères . . . . . . . . . . . . . . . . . . . . . . . 12
1.7.2 Les entiers . . . . . . . . . . . . . . . . . . . . . . . . . 12
1.7.3 Les flottants . . . . . . . . . . . . . . . . . . . . . . . . 13
1.8 Les constantes . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
1.8.1 Les constantes entières . . . . . . . . . . . . . . . . . . 13
1.8.2 Les constantes caractères . . . . . . . . . . . . . . . . . 14
1.8.3 Les constantes flottantes . . . . . . . . . . . . . . . . . 16
1.9 Les chaînes de caractères littérales . . . . . . . . . . . . . . . . 17
1.10 Les constantes nommées . . . . . . . . . . . . . . . . . . . . . 18
1.10.1 Les #define . . . . . . . . . . . . . . . . . . . . . . . . 18
1.10.2 Les énumérations . . . . . . . . . . . . . . . . . . . . . 18
1.11 Les déclarations de variables ayant un type de base . . . . . . 19
1.12 Les opérateurs les plus usuels . . . . . . . . . . . . . . . . . . 20
1.12.1 L’affectation . . . . . . . . . . . . . . . . . . . . . . . . 20
1.12.2 L’addition . . . . . . . . . . . . . . . . . . . . . . . . . 21
iii
1.12.3 La soustraction . . . . . . . . . . . . . . . . . . . . . . 21
1.12.4 La multiplication . . . . . . . . . . . . . . . . . . . . . 22
1.12.5 La division . . . . . . . . . . . . . . . . . . . . . . . . . 22
1.12.6 L’opérateur modulo . . . . . . . . . . . . . . . . . . . . 23
1.12.7 Les opérateurs de comparaison . . . . . . . . . . . . . 23
1.13 Les instructions les plus usuelles . . . . . . . . . . . . . . . . . 24
1.13.1 Instruction expression . . . . . . . . . . . . . . . . . . 24
1.13.2 Instruction composée . . . . . . . . . . . . . . . . . . . 25
1.13.3 Instruction if . . . . . . . . . . . . . . . . . . . . . . . 26
1.14 Inclusion de source . . . . . . . . . . . . . . . . . . . . . . . . 27
1.15 Les procédures et les fonctions . . . . . . . . . . . . . . . . . . 28
1.15.1 Définition d’une fonction . . . . . . . . . . . . . . . . . 28
1.15.2 Appel d’une fonction . . . . . . . . . . . . . . . . . . . 31
1.15.3 Les procédures . . . . . . . . . . . . . . . . . . . . . . 32
1.15.4 Fonctions imbriquées . . . . . . . . . . . . . . . . . . . 33
1.15.5 Récursivité . . . . . . . . . . . . . . . . . . . . . . . . 33
1.15.6 Référence à une fonction externe . . . . . . . . . . . . 33
1.15.7 Comprendre la documentation de la bibliothèque stan-
dard . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33
1.15.8 Les fonctions dans le style K&R . . . . . . . . . . . . . 34
1.16 Impression formattée . . . . . . . . . . . . . . . . . . . . . . . 35
1.17 Structure d’un programme . . . . . . . . . . . . . . . . . . . . 36
1.18 Terminaison d’un programme . . . . . . . . . . . . . . . . . . 38
1.19 Mise en oeuvre du compilateur C sous UNIX . . . . . . . . . . 38
1.20 Exercice . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39
1.21 Récréation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44
2 Les tableaux 47
2.1 Les tableaux . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47
2.1.1 Déclaration de tableaux dont les éléments ont un type
de base . . . . . . . . . . . . . . . . . . . . . . . . . . . 47
2.1.2 Initialisation d’un tableau . . . . . . . . . . . . . . . . 48
2.1.3 Référence à un élément d’un tableau . . . . . . . . . . 50
2.1.4 Chaînes et tableaux de caractères . . . . . . . . . . . . 50
2.2 Les instructions itératives . . . . . . . . . . . . . . . . . . . . 50
2.2.1 Instruction for . . . . . . . . . . . . . . . . . . . . . . 50
iv
2.2.2 Instruction while . . . . . . . . . . . . . . . . . . . . . 52
2.2.3 Instruction do . . . . . . . . . . . . . . . . . . . . . . . 52
2.2.4 Instruction break . . . . . . . . . . . . . . . . . . . . . 53
2.2.5 Instruction continue . . . . . . . . . . . . . . . . . . . 54
2.3 Les opérateurs . . . . . . . . . . . . . . . . . . . . . . . . . . . 54
2.3.1 Opérateur pré et postincrément . . . . . . . . . . . . . 54
2.3.2 Opérateur pré et postdécrément . . . . . . . . . . . . . 55
2.3.3 Quelques utilisations typiques de ces opérateurs . . . . 56
2.3.4 Opérateur et logique . . . . . . . . . . . . . . . . . . . 57
2.3.5 Opérateur ou logique . . . . . . . . . . . . . . . . . . . 58
2.3.6 Opérateur non logique . . . . . . . . . . . . . . . . . . 58
2.4 Exercice . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59
3 Les pointeurs 63
3.1 Notion de pointeur . . . . . . . . . . . . . . . . . . . . . . . . 63
3.2 Déclarations de variables de type pointeur vers les types de base 63
3.3 Type de pointeur générique . . . . . . . . . . . . . . . . . . . 64
3.4 Opérateur adresse de . . . . . . . . . . . . . . . . . . . . . . . 64
3.5 Opérateur d’indirection . . . . . . . . . . . . . . . . . . . . . . 65
3.6 Exercice . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 65
3.7 Pointeurs et opérateurs additifs . . . . . . . . . . . . . . . . . 68
3.7.1 Opérateurs + et - . . . . . . . . . . . . . . . . . . . . . 68
3.7.2 Opérateurs ++ et – . . . . . . . . . . . . . . . . . . . . 69
3.8 Différence de deux pointeurs . . . . . . . . . . . . . . . . . . . 69
3.9 Exercice . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 70
3.10 Passage de paramètres . . . . . . . . . . . . . . . . . . . . . . 73
3.10.1 Les besoins du programmeur . . . . . . . . . . . . . . . 73
3.10.2 Comment les langages de programmation satisfont ces
besoins . . . . . . . . . . . . . . . . . . . . . . . . . . . 73
3.10.3 La stratégie du langage C . . . . . . . . . . . . . . . . 74
3.11 Discussion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 75
3.12 Une dernière précision . . . . . . . . . . . . . . . . . . . . . . 76
3.13 Exercice . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 76
3.14 Lecture formattée . . . . . . . . . . . . . . . . . . . . . . . . . 80
3.15 Les dernières instructions . . . . . . . . . . . . . . . . . . . . . 81
3.15.1 Instruction switch . . . . . . . . . . . . . . . . . . . . 81
v
3.15.2 Instruction goto . . . . . . . . . . . . . . . . . . . . . 84
3.15.3 Instruction nulle . . . . . . . . . . . . . . . . . . . . . 85
3.16 Exercice . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 86
3.17 Récréation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 90
vi
5.3.1 Lecture par caractère : fgetc . . . . . . . . . . . . . . 123
5.3.2 Lecture par caractère : getc . . . . . . . . . . . . . . . 124
5.3.3 Lecture par caractère : getchar . . . . . . . . . . . . . 124
5.3.4 Écriture par caractère : fputc . . . . . . . . . . . . . . 125
5.3.5 Écriture par caractère : putc . . . . . . . . . . . . . . 126
5.3.6 Écriture par caractère : putchar . . . . . . . . . . . . 126
5.4 Lecture et écriture par lignes sur fichier . . . . . . . . . . . . . 126
5.4.1 Lecture par ligne : fgets . . . . . . . . . . . . . . . . 126
5.4.2 Lecture par ligne : gets . . . . . . . . . . . . . . . . . 127
5.4.3 Écriture par chaîne : fputs . . . . . . . . . . . . . . . 128
5.4.4 Écriture par chaîne : puts . . . . . . . . . . . . . . . . 129
5.5 E/S formattées sur fichiers . . . . . . . . . . . . . . . . . . . . 130
5.5.1 Écriture formattée : fprintf . . . . . . . . . . . . . . 130
5.5.2 Écriture formattée : printf . . . . . . . . . . . . . . . 135
5.5.3 Écriture formattée dans une chaîne : sprintf . . . . . 135
5.5.4 Exemples d’utilisation des formats . . . . . . . . . . . 137
5.5.5 Entrées formattées : fscanf . . . . . . . . . . . . . . . 138
5.5.6 Entrées formattées : scanf . . . . . . . . . . . . . . . . 145
5.5.7 Entrées formattées depuis une chaîne : sscanf . . . . . 145
5.6 Récréation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 145
5.7 Exercice 1 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 146
5.8 Exercice 2 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 146
vii
6.9 Passage de structures en paramètre . . . . . . . . . . . . . . . 159
6.10 Détermination de la taille allouée à un type . . . . . . . . . . 160
6.10.1 Retour sur la conversion des tableaux . . . . . . . . . . 161
6.11 Allocation et libération d’espace pour les structures . . . . . . 161
6.11.1 Allocation d’espace : fonctions malloc et calloc . . . 161
6.11.2 Libération d’espace : procédure free . . . . . . . . . . . 163
6.12 Exercice . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 163
6.13 Les champs de bits . . . . . . . . . . . . . . . . . . . . . . . . 169
6.13.1 Généralités . . . . . . . . . . . . . . . . . . . . . . . . 169
6.13.2 Contraintes . . . . . . . . . . . . . . . . . . . . . . . . 170
6.14 Les énumérations . . . . . . . . . . . . . . . . . . . . . . . . . 171
6.15 Les unions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 172
6.16 Accès aux membres de l’union . . . . . . . . . . . . . . . . . . 172
6.17 Utilisation pratique des unions . . . . . . . . . . . . . . . . . . 173
6.18 Une méthode pour alléger l’accès aux membres . . . . . . . . . 174
viii
7.3 Sémantique des expressions . . . . . . . . . . . . . . . . . . . 190
7.3.1 Opérateurs d’adressage . . . . . . . . . . . . . . . . . . 190
7.3.2 Priorité et associativité des opérateurs . . . . . . . . . 191
7.3.3 Ordre d’évaluation des opérandes . . . . . . . . . . . . 192
7.4 Récréation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 192
8 Le préprocesseur 195
8.1 Traitement de macros . . . . . . . . . . . . . . . . . . . . . . . 195
8.1.1 Les macros sans paramètres . . . . . . . . . . . . . . . 195
8.1.2 Macros prédéfinies . . . . . . . . . . . . . . . . . . . . 197
8.1.3 Les macros avec paramètres . . . . . . . . . . . . . . . 198
8.1.4 Les pièges des macros . . . . . . . . . . . . . . . . . . . 201
8.1.5 Macros générant des instructions . . . . . . . . . . . . 202
8.2 Compilation conditionnelle . . . . . . . . . . . . . . . . . . . . 203
8.2.1 Commande #if . . . . . . . . . . . . . . . . . . . . . . 203
8.2.2 Commandes #ifdef et #ifndef . . . . . . . . . . . . . 204
8.2.3 L’opérateur defined . . . . . . . . . . . . . . . . . . . 205
8.2.4 La commande #error . . . . . . . . . . . . . . . . . . 205
8.2.5 Usage . . . . . . . . . . . . . . . . . . . . . . . . . . . 205
8.3 Récréation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 206
ix
9.7.1 Généralités . . . . . . . . . . . . . . . . . . . . . . . . 221
9.7.2 La méthode du langage C . . . . . . . . . . . . . . . . 222
9.8 Définition de types . . . . . . . . . . . . . . . . . . . . . . . . 224
9.9 Utilité des typedef . . . . . . . . . . . . . . . . . . . . . . . . 224
9.9.1 Restriction d’un type de base . . . . . . . . . . . . . . 225
9.9.2 Définition de type structure . . . . . . . . . . . . . . . 225
9.9.3 Définition de types opaques . . . . . . . . . . . . . . . 227
9.10 Qualificatifs de type . . . . . . . . . . . . . . . . . . . . . . . 227
9.11 Fonction à nombre variable de paramètres . . . . . . . . . . . 228
9.11.1 Exemple 1 . . . . . . . . . . . . . . . . . . . . . . . . . 230
9.11.2 Exemple 2 . . . . . . . . . . . . . . . . . . . . . . . . . 231
9.12 Syntaxe des déclarations . . . . . . . . . . . . . . . . . . . . . 232
9.13 Sémantique des déclarations . . . . . . . . . . . . . . . . . . . 236
9.14 Discussion sur les déclarations . . . . . . . . . . . . . . . . . . 237
9.15 En pratique . . . . . . . . . . . . . . . . . . . . . . . . . . . . 238
9.16 Un outil : cdecl . . . . . . . . . . . . . . . . . . . . . . . . . 239
x
10.9 Utilitaires divers <stdlib.h> . . . . . . . . . . . . . . . . . . 246
10.9.1 Conversion de nombres . . . . . . . . . . . . . . . . . . 246
10.9.2 Génération de nombres pseudo-aléatoires . . . . . . . . 247
10.9.3 Gestion de la mémoire . . . . . . . . . . . . . . . . . . 247
10.9.4 Communication avec l’environnement . . . . . . . . . . 247
10.9.5 Recherche et tri . . . . . . . . . . . . . . . . . . . . . . 247
10.9.6 Arithmétique sur les entiers . . . . . . . . . . . . . . . 247
10.9.7 Gestion des caractères multi-octets . . . . . . . . . . . 247
10.10Manipulation de chaînes <string.h> . . . . . . . . . . . . . . 248
10.11Manipulation de la date et de l’heure <time.h> . . . . . . . . 248
B Bibliographie 263
D La grammaire 267
D.1 Les unités lexicales . . . . . . . . . . . . . . . . . . . . . . . . 267
D.2 Les mots-clés . . . . . . . . . . . . . . . . . . . . . . . . . . . 268
xi
D.3 Les identificateurs . . . . . . . . . . . . . . . . . . . . . . . . . 268
D.4 Les constantes . . . . . . . . . . . . . . . . . . . . . . . . . . . 268
D.5 Les chaînes littérales . . . . . . . . . . . . . . . . . . . . . . . 271
D.6 Les opérateurs . . . . . . . . . . . . . . . . . . . . . . . . . . . 272
D.7 La ponctuation . . . . . . . . . . . . . . . . . . . . . . . . . . 272
D.8 Nom de fichier d’inclusion . . . . . . . . . . . . . . . . . . . . 272
D.9 Les nombres du préprocesseur . . . . . . . . . . . . . . . . . . 273
D.10 Les expressions . . . . . . . . . . . . . . . . . . . . . . . . . . 273
D.11 Les déclarations . . . . . . . . . . . . . . . . . . . . . . . . . . 276
D.12 Les instructions . . . . . . . . . . . . . . . . . . . . . . . . . . 280
D.13 Définitions externes . . . . . . . . . . . . . . . . . . . . . . . . 282
D.14 Directives du préprocesseur . . . . . . . . . . . . . . . . . . . 282
D.15 Références croisées de la grammaire . . . . . . . . . . . . . . . 285
F Le bêtisier 297
F.1 Erreur avec les opérateurs . . . . . . . . . . . . . . . . . . . . 297
F.1.1 Erreur sur une comparaison . . . . . . . . . . . . . . . 297
F.1.2 Erreur sur l’affectation . . . . . . . . . . . . . . . . . . 298
F.2 Erreurs avec les macros . . . . . . . . . . . . . . . . . . . . . . 298
F.2.1 Un #define n’est pas une déclaration . . . . . . . . . . 298
F.2.2 Un #define n’est pas une initialisation . . . . . . . . . 299
F.2.3 Erreur sur macro avec paramètres . . . . . . . . . . . . 299
F.2.4 Erreur avec les effets de bord . . . . . . . . . . . . . . 299
F.3 Erreurs avec l’instruction if . . . . . . . . . . . . . . . . . . . 299
F.4 Erreurs avec les commentaires . . . . . . . . . . . . . . . . . . 300
F.5 Erreurs avec les priorités des opérateurs . . . . . . . . . . . . . 301
F.6 Erreur avec l’instruction switch . . . . . . . . . . . . . . . . . 301
xii
F.6.1 Oubli du break . . . . . . . . . . . . . . . . . . . . . . 301
F.6.2 Erreur sur le default . . . . . . . . . . . . . . . . . . 302
F.7 Erreur sur les tableaux multidimensionnels . . . . . . . . . . . 302
F.8 Erreur avec la compilation séparée . . . . . . . . . . . . . . . . 302
xiii
xiv
Avant-propos
Au sujet de l’auteur
Bernard Cassagne, l’auteur original de ce document, est ingénieur retraité du
cnrs et travaillait dans un laboratoire de recherche de l’université de Greno-
ble : le laboratoire LIG, anciennement clips. Le document est maintenant
maintenu par Matthieu Moy, enseignant-chercheur à l’Ensimag et au labora-
toire Verimag. Toute notification d’erreur ou toute proposition d’amélioration
de ce document sera la bienvenue à l’e-mail [email protected].
Au sujet de ce manuel
La littérature technique nous a habitué à deux styles d’écriture de manuels :
le style « manuel de référence » et le style « guide de l’utilisateur » Les
manuels de référence se donnent comme buts d’être exhaustifs et rigoureux.
Les guides de l’utilisateur se donnent comme but d’être didactiques. Cette
partition vient du fait qu’il est quasiment impossible sur des sujets complexes
comme les langages de programmation d’être à la fois rigoureux et didactique.
Pour s’en persuader il suffit de lire le texte d’une norme internationale.
Ce manuel se place dans la catégorie « guide de l’utilisateur » : son but
est de permettre à une personne sachant programmer, d’acquérir les éléments
fondamentaux du langage C. Ce manuel présente donc chaque notion selon
une gradation des difficultés et ne cherche pas à être exhaustif. Il comporte
de nombreux exemples, ainsi que des exercices dont la solution se trouve dans
le corps du texte, mais commence toujours sur une page différente. Le lecteur
peut donc au choix, ne lire les solutions qu’après avoir programmé sa solution
1
personnelle, ou bien lire directement la solution comme si elle faisait partie
du manuel.
Conventions syntaxiques
Les règles de grammaires qui sont données dans le corps de ce manuel sont
simplifiées (dans un but didactique) par rapport à la grammaire officielle du
langage. Cependant, le lecteur trouvera à l’annexe D la grammaire sous une
forme exhaustive et conforme à la norme ansi. La typographie des règles suit
les conventions suivantes :
1. les éléments terminaux du langage seront écrits dans une fonte à largeur
constante, comme ceci : while.
1
C’est masson qui a édité en français [1] et [2] (Cf. Bibliographie)
2
2. les éléments non terminaux du langage seront écrits en italique, comme
ceci :
instruction.
3. les règles de grammaires seront écrites de la manière suivante :
• les parties gauches de règles seront seules sur leur ligne, cadrées à
gauche et suivies du signe deux points (:).
• les différentes parties droites possibles seront introduites par le
signe ⇒ et indentées sur la droite.
Exemple :
instruction :
⇒ if ( expression ) instruction 1
⇒ if ( expression ) instruction 1 else instruc-
tion 2
instruction :
⇒ if ( expression )
instruction 1
⇒ if ( expression )
instruction 1
else instruction 2
3
• les éléments optionnels d’une règle seront indiqués en mettant le
mot « option » (en italique et dans une fonte plus petite) à droite
de l’élément concerné.
Par exemple, la règle :
déclarateur-init :
⇒ déclarateur initialisateur option
déclarateur initialisateur
soit en :
déclarateur
Remerciements
Beaucoup de personnes m’ont aidé à améliorer le manuscrit original en me sig-
nalant de nombreuses erreurs et en me proposant des améliorations. Qu’elles
soient toutes remerciées de leurs lectures attentives et amicalement critiques,
tout particulièrement Michel Burlet, Damien Genthial, Fabienne Lagnier,
Xavier Nicollin et Serge Rouveyrol.
4
Chapter 1
Les bases
5
C89, tantôt C90).
Cette seconde version du langage C devrait donc s’appeler ISO C, mais
comme les acteurs importants du monde informatique sont de culture anglo-
saxonne et que ceux-ci persistent à l’appeler ansi c, (presque ?) tout le monde
fait de même. Dans ce manuel, nous suivrons l’usage général, et utiliserons
l’expression ansi c pour désigner la norme commune à l’ansi et l’ISO.
De nouvelles versions de la norme ISO, peu utilisées en pratique, sont
sorties en 1999 et en 2011. Les extensions qu’elles apportent sont décrites
dans le chapitre 11.
Ce document décrit C90 ansi, avec parfois des références à C K&R,
de manière à permettre au lecteur de comprendre les sources écrits avant
l’apparition de la norme.
6
pour rendre des services du type inclusion de source, compilation con-
ditionnelle, et traitement de macros ;
7
être utilisés comme identificateurs. La liste exhaustive des mots-clés est la
suivante :
Attention
Si le compilateur produit un message d’erreur syntaxique incompréhensible
il est recommandé d’avoir le réflexe de consulter la liste des mots clés pour
vérifier que l’on n’a pas pris comme identificateur un mot-clé. Si le lecteur
désire être convaincu, il lui est suggéré de donner le nom long à une variable
entière.
8
surcroît, la distinction entre minuscules et majuscules n’est pas garantie (au
contraire des noms internes, pour lesquels cette distinction est garantie).
/*
* Copyright (c) 1991, Larry Wall
*
* You may distribute under the terms of either the
* GNU General Public License or the Artistic License,
* as specified in the README file.
*/
9
Pour le niveau de la procédure, je trouve agréable de réaliser des espèces
de cartouches permettant de découper visuellement un listing en ses
différentes procédures, comme ceci par exemple :
/*************************************************/
/* */
/* strcpy */
/* */
/* But: */
/* copie une chaîne dans une autre */
/* */
/* Interface: */
/* s1 : chaîne destination */
/* s2 : chaîne source */
/* */
/*************************************************/
/*
* If nothing above worked, then we get desperate. We
* attempt to open the stupid font at one of a small set
* of predefined sizes, and then use PostScript scaling to
* generate the correct size.
*
* We much prefer scaling up to scaling down, since
* scaling down can omit character features, so we try the
* larger sizes first, and then work down.
*/
10
char *name; /* Function unit name. */
struct function_unit *next; /* Next function unit. */
int multiplicity; /* Number of units of this type. */
int simultaneity; /* Maximum number of simultaneous insns
on this function unit or 0 if unlimited.
struct range ready_cost; /* Range of ready cost values. */
struct range issue_delay; /* Range of issue delay values. */
Attention
Exemple :
instruction
/* premier commentaire
instruction
...
instruction
/* second commentaire */
instruction
4
Dans [5], Peter Van der Linden donne un exemple amusant de ce bug. Dans un
compilateur, l’efficacité de l’algorithme de hachage de la table des identificateurs dépendait
de la bonne initialisation d’une variable. Dans le code initial, l’initialisation avait été mise
involontairement dans un commentaire provoquant une initialisation par défaut à zéro. La
simple correction du bug, fit gagner 15% d’efficacité au compilateur !
5
Voir cependant F.4
11
1.7 Les types de base
1.7.1 Les caractères
Le mot-clé désignant les caractères est char. Un objet de ce type doit pouvoir
contenir le code de n’importe quel caractère de l’ensemble des caractères
utilisé sur la machine. Le codage des caractères n’est pas défini par le langage :
c’est un choix d’implémentation. Cependant, dans la grande majorité des cas,
le code utilisé est le code dit ascii, ou un surensemble comme par exemple
la norme iso-8859.
Attention
Le type caractère est original par rapport à ce qui se fait habituellement dans
les langages de programmation. La norme précise clairement qu’un objet de
type caractère peut être utilisé dans toute expression où un objet de type
entier peut être utilisé. Par exemple, si c est de type char, il est valide
d’écrire c + 1 : cela donnera le caractère suivant dans le code utilisé sur la
machine.
12
unsigned int permettent de contenir des entiers non signés en représentation
binaire.
On peut combiner attribut de précision et attribut de représentation et
avoir par exemple un unsigned long int. En résumé, on dispose donc de six
types d’entiers : int, short int, long int (tous trois signés) et unsigned
int, unsigned short int et unsigned long int.
• Sémantique :
Le type d’une constante entière est le premier type, choisi dans une liste
de types, permettant de représenter la constante :
13
forme de la constante liste de types
pas de suffixe, décimal int, long int, unsigned long i
pas de suffixe, octal ou hexadécimal int, unsigned int, long int, un
suffixé par u ou U unsigned int, unsigned long in
suffixé par l ou L long int, unsigned long int
suffixé par (u ou U) et (l ou L) unsigned long int
Attention
Ces conventions d’écriture des constantes ne respectent pas l’écriture mathé-
matique, puisque 010 devant être interprété en octal, n’est pas égal à 10.
Les cas particuliers sont traités par une séquence d’échappement intro-
duite par le caractère \.
14
constante caractère sémantique
’\x0A’ newline
’\x0D’ return
’\x1B’ escape
3. Certains d’entre eux, utilisés très fréquemment, disposent
d’une notation particulière. Il s’agit des caractères suivants :
constante caractère sémantique
’\n’ new line
’\t’ horizontal tabulation
’\v’ vertical tabulation
’\b’ back space
’\r’ carriage return
’\f’ form feed
’\a’ audible alert
– Caractères disposant d’une représentation imprimable mais devant
être désignés par une séquence d’échappement.
constante caractère sémantique
’\’’ ’
’\\’ \
– Caractères disposant d’une représentation imprimable et pouvant
être désignés soit par une séquence d’échappement soit par eux-
mêmes.
constante caractère sémantique
’\"’ ou ’"’ "
’\?’ ou ’?’ ?
• Sémantique :
Une constante caractère est de type int et a pour valeur le code du
caractère dans le codage utilisé par la machine.
Note
Pourquoi diable les deux caractères " et ? disposent ils de deux notations
possibles ?
15
Le caractère " peut être représenté par la notation ’\"’ parce que celle-ci
doit être utilisée dans les chaînes de caractères (voir plus loin 1.9). Pour des
raisons de symétrie, les concepteurs du langage n’ont pas voulu qu’une nota-
tion valable pour une chaînes de caractères ne soit pas valable pour un carac-
tère. Le caractère ? était historiquement particulier à cause de l’existance des
trigraphes 6 , tombés en désuétude et désactivés par défaut sur les compilateurs
modernes.
• Sémantique :
Une constante non suffixée a le type double. Une constante suffixée
par f ou F a le type float. Une constante suffixée par l ou L a le type
long double.
La valeur de la constante mantisse e exposant est mantisse ×10exposant .
Si la valeur résultante ne correspond pas au type, la valeur est ar-
rondie vers une valeur supérieure ou inférieure (le choix dépend de
l’implémentation).
• Exemples :
6
http://en.wikipedia.org/wiki/Digraphs_and_trigraphs
16
notation C notation mathématique
2. 2
.3 0.3
2.3 2.3
2e4 2 × 104
2.e4 2 × 104
.3e4 0.3 × 104
2.3e4 2.3 × 104
2.3e-4 2.3 × 10−4
17
1.10 Les constantes nommées
Il y a deux façons de donner un nom à une constante : soit en utilisant les
possibilités du préprocesseur, soit en utilisant des énumérations.
Attention
Une telle définition de constante n’est pas une déclaration mais une com-
mande du préprocesseur. Il n’y a donc pas de ; à la fin. Rappelons que le
préprocesseur ne compile pas, il fait des transformations d’ordre purement
textuel. Si on écrit :
#define PI 3.14159;
le préprocesseur remplacera toute utilisation de PI par 3.14159; et par ex-
emple, remplacera l’expression PI / 2 par 3.14159; / 2 ce qui est une ex-
pression incorrecte. Dans une telle situation, le message d’erreur ne sera pas
émis sur la ligne fautive (le #define), mais sur une ligne correcte (celle qui
contient l’expression PI / 2), ce qui gênera la détection de l’erreur.
18
enum {LUNDI, MARDI, MERCREDI, JEUDI, VENDREDI, SAMEDI, DIMANCHE};
définit les identificateurs LUNDI, ... DIMANCHE comme étant des constantes de
type int, et leur donne les valeurs 0, 1, ... 6. Si on désire donner des valeurs
particulières aux constantes, cela est possible :
Remarque
Il est d’usage (au moins dans le monde unix) de donner un nom entièrement
en majuscules aux constantes nommées d’un programme. Mon opinion est
que ceci est une bonne convention, qui accroît la lisibilité des programmes.
Il est possible de donner une valeur initiale aux variables ainsi déclarées.
Exemple :
int i = 54;
int i = 34, j = 12;
19
1.12 Les opérateurs les plus usuels
1.12.1 L’affectation
En C, l’affectation est un opérateur et non pas une instruction.
• Syntaxe :
expression :
⇒ lvalue = expression
Dans le jargon C, une lvalue est une expression qui doit délivrer
une variable (par opposition à une constante). Une lvalue peut être
par exemple une variable simple, un élément de tableau, mais pas
une constante. Cette notion permet d’exprimer dans la grammaire
l’impossibilité d’écrire des choses du genre 1 = i qui n’ont pas de sens.
Exemples d’affectation :
i = 3
f = 3.4
i = j + 1
• Sémantique :
L’opérateur d’affectation a deux effets :
Exemple :
i = (j = k) + 1
20
• Conversions de type :
Lorsque la valeur de l’expression est affectée à la lvalue, la valeur est
éventuellement convertie dans le type de la lvalue. On peut par exemple
affecter une expression entière à un flottant.
1.12.2 L’addition
• Syntaxe :
expression :
⇒ + expression
⇒ expression 1 + expression 2
• Sémantique :
Les deux expressions sont évaluées, l’addition réalisée, et la valeur
obtenue est la valeur de l’expression d’addition. (La sémantique de
+ expression est celle de 0 + expression).
L’ordre dans lequel les deux expressions sont évaluées, n’est pas déter-
miné. Si expression 1 et expression 2 font des effets de bords, on n’est
donc pas assuré de l’ordre dans lequel ils se feront.
Après évaluation des expressions, il peut y avoir conversion de type
de l’un des opérandes, de manière à permettre l’addition. On pourra
par exemple faire la somme d’une expression délivrant un flottant et
d’une expression délivrant un entier : l’entier sera converti en flottant
et l’addition sera réalisée entre flottants.
1.12.3 La soustraction
• Syntaxe :
L’opérateur peut être utilisé de manière unaire ou binaire :
expression :
⇒ - expression
⇒ expression 1 - expression 2
21
• Sémantique :
Les deux expressions sont évaluées, la soustraction réalisée, et la valeur
obtenue est la valeur de l’expression soustraction. (La sémantique de -
expression est celle de 0 - expression).
Les mêmes remarques concernant l’ordre d’évaluation des opérandes
ainsi que les éventuelles conversions de type faites au sujet de l’addition
s’appliquent à la soustraction.
1.12.4 La multiplication
• Syntaxe :
expression :
⇒ expression 1 * expression 2
• Sémantique :
Les deux expressions sont évaluées, la multiplication réalisée, et la valeur
obtenue est la valeur de l’expression multiplicative.
Les mêmes remarques concernant l’ordre d’évaluation des opérandes
ainsi que les éventuelles conversions de type faites au sujet de l’addition
s’appliquent à la multiplication.
1.12.5 La division
• Syntaxe :
expression :
⇒ expression 1 / expression 2
• Sémantique :
Contrairement à d’autres langages, le langage C ne dispose que d’une
seule notation pour désigner deux opérateurs différents : le signe /
désigne à la fois la division entière et la division entre flottants.
Si expression 1 et expression 2 délivrent deux valeurs entières, alors il
s’agit d’une division entière. Si l’une des deux expressions au moins
délivre une valeur flottante, il s’agit d’une division entre flottants.
22
Dans le cas de la division entière, si les deux opérandes sont positifs,
l’arrondi se fait vers zéro, mais si au moins un des deux opérandes est
négatif, la façon dont se fait l’arrondi dépend de l’implémentation (mais
il est généralement fait vers zéro). Exemple : 13 / 2 délivre la valeur
6 et -13 / 2 ou 13 / -2 peuvent délivrer -6 ou -7 mais le résultat sera
généralement -6.
Les remarques concernant l’ordre d’évaluation des opérandes faites au
sujet de l’addition s’appliquent également à la division. Les remarques
concernant les éventuelles conversion de type faites au sujet de l’addition
s’appliquent à la division entre flottants.
23
expression :
⇒ expression 1 opérateur expression 2
où opérateur peut être l’un des symboles suivants :
opérateur sémantique
> strictement supérieur
< strictement inférieur
>= supérieur ou égal
<= inférieur ou égal
== égal
!= différent
• Sémantique :
Les deux expressions sont évaluées puis comparées, la valeur rendue est
de type int et vaut 1 si la condition est vraie, et 0 sinon.
On remarquera que le type du résultat est int, car le type booléen
n’existe pas (sauf en C99, cf. section ??, mais il est peu utilisé en
pratique).
• Sémantique :
L’expression est évaluée, et sa valeur est ignorée. Ceci n’a donc de sens
que si l’expression réalise un effet de bord. Dans la majorité des cas, il
s’agira d’une expression d’affectation. Exemple :
i = j + 1;
24
Remarque
• Sémantique :
Le but de l’instruction composée est double, elle permet :
– les accolades jouent le rôle des mots clés begin et end que l’on trouve
dans certains langages (Pascal, PL/1, etc.).
25
1.13.3 Instruction if
• Syntaxe :
instruction :
⇒ if ( expression ) instruction 1
⇒ if ( expression ) instruction 1 else instruction 2
• Sémantique :
expression est évaluée, si la valeur rendue est non nulle, on exécute
instruction 1 , sinon on exécute instruction 2 si elle existe.
26
Exemples d’instructions if
if (x > y)
{
... /* liste d’instructions */
}
else
{
... /* liste d’instructions */
}
if (a) /* équivalent à if (a != 0) */
{
...
}
27
#include <nom-de-fichier>
il remplace cette ligne par le contenu du fichier nom-de-fichier.
La manière dont se fait la recherche du fichier à inclure est dépendante de
l’implémentation. Dans le système unix, il est traditionnel de se conformer
à la règle suivante :
• si on utilise < et >, le fichier est un fichier système, il sera recherché dans
un ou plusieurs répertoires systèmes connus du compilateur, comme par
exemple /usr/include.
• Sémantique :
type est le type de la valeur rendue par la fonction ; identificateur est
le nom de la fonction ; liste-de-déclarations-de-paramètres est la liste
(séparés par des virgules) des déclarations des paramètres formels.
28
La liste-de-déclarations option permet si besoin est, de déclarer des
variables qui seront locales à la fonction, elles seront donc inaccessibles
de l’extérieur. La liste-d’instructions est l’ensemble des instructions
qui seront exécutées sur appel de la fonction. Parmi ces instructions, il
doit y avoir au moins une instruction du type :
return expression ;
Exemple
int sum_square(int i,int j) /* la fonction sum_square délivre u
/* ses paramètres formels sont les i
{
int resultat; /* déclaration des variables locale
Instruction return
L’instruction return est une instruction comme une autre, il est donc possible
d’en utiliser autant qu’on le désire dans le corps d’une fonction. Exemple :
Si la dernière instruction exécutée par une fonction n’est pas une instruc-
tion return, la valeur rendue par la fonction est indéterminée.
29
Absence de paramètre formel
Exemple en C ansi :
Attention à la syntaxe
La syntaxe des déclarations pour les paramètres formels et les variables n’est
pas la même. Quand on a besoin de déclarer deux variables du même type,
on peut utiliser deux déclarations : int i; int j; ou bien une seule décla-
ration : int i,j. Aucune de ces deux possibiltés n’est acceptable pour une
liste de paramètres formels. On rappelle qu’il faut écrire :
30
1.15.2 Appel d’une fonction
• Syntaxe :
expression :
⇒ identificateur ( liste-d’expressions )
• Sémantique :
Les expressions de liste-d’expressions sont évaluées, puis passées en tant
que paramètres effectifs à la fonction de nom identificateur, qui est
ensuite exécutée. La valeur rendue par la fonction est la valeur de
l’expression appel de fonction.
• Exemple :
{
int a,b,m,s;
double d;
Attention
1. Dans le cas d’une fonction sans paramètre, la liste-d’expressions doit
être vide : il n’est pas possible d’utiliser le mot clé void en tant que
paramètre effectif.
sum_square(f(x),g(y));
31
1.15.3 Les procédures
Le langage C ne comporte pas à strictement parler le concept de procédure.
Cependant, les fonctions pouvant réaliser sans aucune restriction tout effet
de bord qu’elles désirent, le programmeur peut réaliser une procédure à l’aide
d’une fonction qui ne rendra aucune valeur. Pour exprimer l’idée de « aucune
valeur », on utilise le mot-clé void. Une procédure sera donc implémentée sous
la forme d’une fonction retournant void et dont la partie liste-d’instructions
ne comportera pas d’instruction return.
Lors de l’appel de la procédure, il faudra ignorer la valeur rendue c’est à
dire ne pas l’englober dans une expression.
Exemple
void print_add(int i,int j) /* la procédure et ses
paramètres formels */
{
int r; /* une variable locale à print_add */
r = i + j;
... /* instruction pour imprimer la valeur de r */
}
a = 12; b = 45;
print_add(a,b); /* appel de print_add */
print_add(13,67); /* un autre appel à print_add */
}
Problème de vocabulaire
Dans la suite du texte, nous utiliserons le terme de fonction pour désigner
indifféremment une procédure ou une fonction, chaque fois qu’il ne sera pas
nécessaire de faire la distinction entre les deux.
32
1.15.4 Fonctions imbriquées
À l’inverse de certains langages, les fonctions imbriquées n’existent pas dans
le langage C. Il n’est donc pas possible qu’une fonction ne soit connue qu’à
l’intérieur d’une autre fonction.
1.15.5 Récursivité
Il n’y a rien de spécial à faire à la déclaration d’une fonction pour qu’elle
puisse être appelée de manière récursive. Voici un exemple de factorielle :
int facto(int n)
{
if (n == 1) return(1);
else return(n * facto(n-1));
}
33
l’interface d’appel de la fonction se trouve dans le paragraphe Synopsis.
Dans ces paragraphes, se trouve toujours une commande d’inclusion de source.
Exemple de la fonction cosinus :
Synopsis
#include <math.h>
double cos(double x);
Il faut comprendre que le fichier math.h contient un ensemble de définitions
nécessaires à l’utilisation de cos, en particulier la déclaration de cos en fonc-
tion externe, à savoir :
extern double cos(double x);
Il faut donc inclure le fichier math.h avant toute utilisation de la fonction
cos.
34
Dans ce cas, le compilateur ne connaît pas le type des paramètres de la
fonction, il lui est donc impossible de détecter une erreur portant sur le type
des paramètres lors d’un appel de fonction externe. Cette situation est assez
catastrophique et c’est la raison pour laquelle le comité de normalisation a
introduit le concept de prototype de fonction vu au chapitre 1.15.1. Il n’a
cependant pas voulu faire un changement incompatible, il a donc décidé que
les deux méthodes étaient acceptées, en précisant toutefois que la méthode
k&r était une caractéristique obsolescente. En clair, cela signifie que cette
méthode pourrait être abandonnée dans une prochaine révision de la norme,
même si ça n’a pas été le cas pour C99 et C11.
Remarque
Nous ne pouvons que conseiller l’utilisation exclusive des prototypes de fonc-
tions, et de ne jamais utiliser la méthode k&r. Si nous avons exposé celle-ci,
c’est pour permettre au lecteur de comprendre les quantités énormes de source
C existant, qui sont écrites à la k&r.
int i = 12;
int j = 32;
printf("la valeur de i est %d et celle de j est %d",i,j);
35
imprimera :
Il est possible d’avoir une liste-d’expressions vide, dans ce cas l’appel à printf
devient :
printf ( chaîne-de-caractères ) ;
par exemple, pour imprimer le mot erreur en le soulignant7 , on peut écrire :
Fichier d’include
La déclaration de printf en tant que fonction externe se trouve dans le fichier
stdio.h qu’il faudra inclure avant toute utilisation. En tête du programme
il faudra donc mettre :
#include <stdio.h>
programme :
⇒ liste-de-déclarations option
7
à condition que la sortie se fasse sur un terminal papier
36
liste-de-définitions-de-fonctions
37
1.18 Terminaison d’un programme
On peut réaliser la terminaison d’un programme en appelant la fonction
exit qui fait partie de la bibliothèque standard. Cette fonction admet un
paramètre entier qui est un code indiquant le type de la terminaison : une
valeur nulle indique que le programme s’est convenablement terminé, toute
valeur non nulle indique une terminaison avec erreur. Cette valeur est trans-
mise à l’environnement d’exécution du programme 8 d’une manière dépen-
dante de l’implémentation.
Un retour de la fonction main est équivalent à un appel à la fonction exit
en lui passant en paramètre la valeur retournée par main. Si l’environnement
d’exécution teste la valeur rendue par le programme, il faut donc terminer la
fonction main par return 0;.
cc -o essai1 essai1.c
essai1
Pour vérifier qu’il n’y a pas de problème pour la mise en œuvre du compila-
teur, on peut essayer sur un des plus petits programmes possibles, à savoir
un programme sans variables globales, et n’ayant qu’une seule procédure qui
sera la procédure main. Exemple :
8
Sous unix ce sera le shell de l’utilisateur
38
#include <stdio.h>
int main(void)
{
printf("ça marche!!\n");
return 0;
}
La valeur rendue par main (qui est ici 0) a une sémantique qui dépend du
système d’exploitation. Sous unix, la convention est la suivante :
• toute valeur différente de 0 est le signe que l’exécution n’a pu aller à son
terme normal, et cette valeur est un code de l’erreur qui s’est produite.
1.20 Exercice
Écrire un programme comportant :
39
40
#include <stdio.h>
int heures, minutes, secondes;
/**********************************************************************
/*
/* print_heure
/*
/* But:
/* Imprime l’heure
/*
/* Interface:
/* Utilise les variables globales heures, minutes, secondes
/*
/**********************************************************************
void print_heure(void)
{
printf("Il est %d heure",heures);
if (heures > 1) printf("s");
printf(" %d minute",minutes);
if (minutes > 1) printf("s");
printf(" %d seconde",secondes);
if (secondes > 1) printf("s");
printf("\n");
}
/**********************************************************************
/*
/* set_heure
/*
/* But:
/* Met l’heure à une certaine valeur
/*
/* Interface:
/* h, m, s sont les valeurs à donner à heures, minutes, secondes
/*
/**********************************************************************
void set_heure(int h, int m, int s)
{
41
heures = h; minutes = m; secondes = s;
}
/**********************************************************************
/*
/* tick
/*
/* But:
/* Incrémente l’heure de une seconde
/*
/* Interface:
/* Utilise les variables globales heures, minutes, secondes
/*
/**********************************************************************
void tick(void)
{
secondes = secondes + 1;
if (secondes >= 60)
{
secondes = 0;
minutes = minutes + 1;
if (minutes >= 60)
{
minutes = 0;
heures = heures + 1;
if (heures >= 24) heures = 0;
}
}
}
/**********************************************************************
/*
/* main
/*
/**********************************************************************
int main(void)
{
set_heure(3,32,10);
42
tick();
print_heure();
set_heure(1,32,59);
tick();
print_heure();
set_heure(3,59,59);
tick();
print_heure();
set_heure(23,59,59);
tick();
print_heure();
return(0);
}
43
1.21 Récréation
Ami lecteur, si vous m’avez suivi jusqu’au bout de ce chapitre indigeste, vous
méritez un divertissement.
Tous les ans, est organisé sur Internet le concours international du code
C le plus obscur (International Obfuscated C Code Competition, ioccc en
abrégé). Le but est de produire un programme qui se compile et s’exécute
sans erreur, dont le source est volontairement le plus obscur possible (ce qui
est facile), mais qui est « intéressant » à un titre ou à un autre. C’est sur
ce dernier point que doit s’exercer la créativité des participants. Tous les
ans, les meilleures contributions sont de véritables joyaux et sont librement
accessibles via l’url : ftp://ftp.uu.net/pub/ioccc.
Nous présentons ci-dessous la contribution de Brian Westley pour l’année
1990. Son programme a ceci d’extraordinaire que le source C peut se lire
comme un texte en « anglais » : il s’agit d’une conversation épistolaire entre
un amoureux et sa belle (rétive, hélas pour lui). Il y a quelques petites licences
prises par rapport à du vrai anglais : 1s est pris comme une approximation
du mot anglais is, 1l est pris comme approximation de ll (dans I’ll get a
break) et des signes cabalistiques sont introduits entre les mots, mais c’est
parfaitement lisible.
Il y a aussi un problème avec le langage C : le compilateur utilisé en
1990 par B. Westley n’était pas ansi et acceptait le suffixe s derrière une
constante entière pour lui donner le type short int. Si on transforme les 1s
en 1, le programme se compile et s’exécute sans erreur. L’exécutable est un
programme ... d’effeuillage de la marguerite ! Il faut lui passer en paramètre
un nombre sensé être le nombre de pétales de la marguerite, et le programme
va « dire » : love me, love me not, love me, love me not etc. 9 un nombre de
fois égal au nombre de pétales. Voici le texte du programme :
char*lie;
44
dear; (char)lotte--;
for(get= !me;; not){
1 - out & out ;lie;{
char lotte, my= dear,
**let= !!me *!not+ ++die;
(char*)(lie=
"The gloves are OFF this time, I detest you, snot\n\0sed GEEK!");
(char)lotte-
(char)*lie++;
for (*((char*)&lotte)^=
(char)lotte; (love ly) [(char)++lotte+
!!0xBABE];){ if (’I’ -lie[ 2 +(char)lotte]){ ’I’-1l ***die; }
else{ if (’I’ * get *out* (’I’-1l **die[ 2 ])) *((char*)&lotte) -=
’4’ - (’I’-1l); not; for(get=!
45
get; !out; (char)*lie & 0xD0- !not) return!!
(char)lotte;}
(char)lotte;
do{ not* putchar(lie [out
*!not* !!me +(char)lotte]);
not; for(;!’a’;);}while(
love (char*)lie);{
exit( (char)lotte);}
46
Chapter 2
Les tableaux
Exemple :
47
En pratique, il est recommandé de toujours donner un nom à la constante
qui indique le nombre d’éléments d’un tableau. Exemple :
#define N 100
int t[N];
#define N 5
int t[N] = {1, 2, 3, 4, 5};
#define N 10
int t[N] = {1, 2};
48
Cas particulier des tableaux de caractères
• Un tableau de caractères peut être initialisé par une liste de constantes
caractères. Exemple :
49
2.1.3 Référence à un élément d’un tableau
• Syntaxe :
Dans sa forme la plus simple, une référence à un élément de tableau a
la syntaxe suivante :
expression :
⇒ nom-de-tableau [ expression 1 ]
#define N 10
int t[N];
on peut écrire :
50
instruction :
⇒ for ( expression 1 option ; expression 2 option ; expres-
sion 3 option )
instruction
• Sémantique : l’exécution réalisée correspond à l’organigramme suivant :
?
expression1
-
?
expression2 != 0 ? non
- fin du for
oui
?
instruction
?
expression3
Remarques
On voit que la vocation de expression 1 et expression 3 est de réaliser des effets
de bord, puisque leur valeur est inutilisée. Leur fonction logique est d’être re-
spectivement les parties initialisation et itération de la boucle. L’ expression 2
est utilisée pour le test de bouclage. L’instruction est le travail de la boucle.
51
Exemple de boucle for
Initialisation d’un tableau à zéro :
#define N 10
int t[N];
for (i = 0; i < N; i = i + 1) t[i] = 0;
• Sémantique :
l’exécution réalisée correspond à l’organigramme suivant :
?
- expression != 0 ? non
- fin du while
oui
?
instruction
2.2.3 Instruction do
• Syntaxe :
instruction :
⇒ do instruction while ( expression ) ;
52
• Sémantique :
l’exécution réalisée correspond à l’organigramme suivant :
?
- instruction
?
expression != 0 ? non
- fin du do
oui
• Sémantique :
Provoque l’arrêt de la première instruction for, while, do englobante.
Exemple
L’instruction for ci-dessous est stoppée au premier i tel que t[i] est nul :
53
for (i = 0; i < N; i = i + 1)
if (t[i] == 0) break;
• Sémantique :
Dans une instruction for, while ou do, l’instruction continue provoque
l’arrêt de l’itération courante, et le passage au début de l’itération suiv-
ante.
Exemple
Supposons que l’on parcoure un tableau t pour réaliser un certain traitement
sur tous les éléments, sauf ceux qui sont négatifs :
for (i = 0; i < N; i = i + 1)
{
/* on passe au i suivant dans le for */
if (t[i] < 0 ) continue;
... /* traitement de l’élément courant */
}
54
++ lvalue incrémente lvalue de 1 et délivre cette nouvelle valeur.
lvalue ++ incrémente lvalue de 1 et délivre la valeur initiale de lvalue.
Exemples
Soient i un int et t un tableau de int :
i = 0;
t[i++] = 0; /* met à zéro l’élément d’indice 0 */
t[i++] = 0; /* met à zéro l’élément d’indice 1 */
i = 1;
t[++i] = 0; /* met à zéro l’élément d’indice 2 */
t[++i] = 0; /* met à zéro l’élément d’indice 3 */
Exemples
Soient i un int et t un tableau de int :
i = 9;
t[i--] = 0; /* met à zéro l’élément d’indice 9 */
t[i--] = 0; /* met à zéro l’élément d’indice 8 */
55
i = 8;
t[--i] = 0; /* met à zéro l’élément d’indice 7 */
t[--i] = 0; /* met à zéro l’élément d’indice 6 */
i++; /* incrémentation de i */
j--; /* décrémentation de j */
56
2.3.4 Opérateur et logique
• Syntaxe :
expression :
⇒ expression 1 && expression 2
• Sémantique :
expression1 est évaluée et :
(on boucle tant que ce test est vrai), où i < n est le test permettant de ne
pas déborder du tableau, et t[i] != 234 est le test de l’élément recherché.
S’il n’existe dans le tableau aucun élément égal à 234, il va arriver un mo-
ment où on va évaluer i < n && t[i] != 234 avec i = n. La sémantique de
l’opérateur && assurant que l’expression t[i] != 234 ne sera pas évaluée, on
ne court donc pas le risque d’avoir une erreur matérielle (t[n] peut référencer
une adresse mémoire invalide).
57
Exemples d’utilisation
int a,b;
if (a > 32 && b < 64) ...
if ( a && b > 1) ...
b = (a > 32 && b < 64);
• Sémantique :
expression 1 est évaluée et :
Exemples
int a,b;
if (a > 32 || b < 64) ...
if ( a || b > 1) ...
b = (a > 32 || b < 64);
58
expression :
⇒ ! expression
• Sémantique :
expression est évaluée, si sa valeur est nulle, l’opérateur ! délivre la
valeur 1, sinon il délivre la valeur 0.
Cet opérateur réalise le non logique de son opérande.
2.4 Exercice
Déclarer un tableau nb_jour qui doit être initialisé de façon à ce que
nb_jour[i] soit égal au nombre de jours du ieme mois de l’année pour i
allant de 1 à 12 (nb_jour[0] sera inutilisé).
Écrire une procédure d’initialisation de nb_jour qui utilisera l’algorithme
suivant :
59
#include <stdio.h>
int nb_jours[13];
/**********************************************************************
/*
/* init_nb_jours
/*
/* But:
/* Initialise le tableau nb_jours
/*
/**********************************************************************
void init_nb_jours(void)
{
int i;
/**********************************************************************
/*
/* print_nb_jours
/*
/* But:
/* Imprime le contenu du tableau nb_jours
/*
/**********************************************************************
void print_nb_jours(void)
{
int i;
60
}
/**********************************************************************
/*
/* main
/*
/**********************************************************************
int main(void)
{
init_nb_jours();
print_nb_jours();
return 0;
}
61
62
Chapter 3
Les pointeurs
variable de type
pointeur vers un int int
- 372
63
Exemple :
Note
La solution quand on utilisait les compilateurs K&R était d’utiliser le type
char * qui jouait de manière conventionnelle ce rôle de pointeur générique.
Le lecteur ne s’étonnera donc pas de trouver dans les vieux (?) manuels, la
fonction malloc définie de la manière suivante :
char *malloc(size);
int i;
int *pi; /* pi pointeur vers un int */
1
size_t est un type défini dans stddef.h
64
pi = &i; /* le pointeur pi repère la variable i */
int i;
int *pi;
Remarque
L’opérateur * est surchargé : il peut être opérateur de multiplication ou
opérateur d’indirection. La grammaire lève l’ambiguïté car l’opérateur
d’indirection est préfixé, alors que l’opérateur de multiplication est infixé.
Surcharger les opérateurs gêne la lisibilité des programmes. Exemple : si
i et j sont des pointeurs vers des entier, la multiplication des deux valeurs
pointées s’écrit : *i**j
Devinette
Si i et j sont des pointeurs vers des entiers, *i**j est le produit des valeurs
pointées, mais *i/*j est-il le quotient des valeurs pointées ? Réponse à la fin
de ce chapitre.
3.6 Exercice
1. Déclarer un entier i et un pointeur p vers un entier ;
65
3. Imprimer la valeur de i ;
5. Imprimer la valeur de i.
66
#include <stdio.h>
/**********************************************************************
/*
/* main
/*
/**********************************************************************
int main()
{
int i;
int *p;
i = 1;
p = &i;
67
3.7 Pointeurs et opérateurs additifs
3.7.1 Opérateurs + et -
L’opérateur + permet de réaliser la somme de deux valeurs arithmétiques,
mais il permet également de réaliser la somme d’un pointeur et d’un entier.
Une telle opération n’a de sens cependant, que si le pointeur repère un élément
d’un tableau.
Soient p une valeur pointeur vers des objets de type T et un tableau dont
les éléments sont du même type T ; si p repère l’élément d’indice i du tableau,
p + j est une valeur de type pointeur vers T, qui repère l’élément d’indice
i + j du tableau (en supposant qu’il existe).
Il en va de même avec l’opérateur soustraction : si p repère l’élément
d’indice i d’un tableau, p - j repère l’élément d’indice i - j du tableau (toujours
en supposant qu’il existe). Exemple :
#define N 10
int t[N];
int *p,*q,*r,*s;
68
3.7.2 Opérateurs ++ et –
On peut appliquer les opérateurs ++ et – à des pointeurs et il est classique de
les utiliser pour réaliser des parcours de tableaux. Exemple (on rappelle que
toute chaîne est terminée par un null, c’est à dire le caractère ’\0’) :
p = &mess[0];
while (*p != ’\0’)
{
/* ici utilisation de *p++ */
}
69
3.9 Exercice
Déclarer et initialiser statiquement un tableau d’entiers t avec des valeurs
dont certaines seront nulles. Écrire une procédure qui parcoure le tableau t
et qui imprime les index des éléments nuls du tableau, sans utiliser aucune
variable de type entier. Une solution possible est donnée page suivante.
70
#include <stdio.h>
#define N 10
int t[N] = {1,2,0,11,0,12,13,14,0,4};
/**********************************************************************
/*
/* print1
/* Premiere version
/*
/**********************************************************************
void print1(void)
{
int *pdeb,*pfin,*p;
/**********************************************************************
/*
/* print2
/* Une autre version
/*
/**********************************************************************
void print2(void)
{
int *pdeb,*pfin,*p;
71
printf("\n");
}
/**********************************************************************
/*
/* main
/*
/**********************************************************************
int main(void)
{
print1();
print2();
return 0;
}
72
3.10 Passage de paramètres
3.10.1 Les besoins du programmeur
En ce qui concerne le passage de paramètres à une procédure, le programmeur
a deux besoins fondamentaux :
– soit il désire passer une valeur qui sera exploitée par l’algorithme de la
procédure (c’est ce dont on a besoin quand on écrit par exemple sin(x)).
Une telle façon de passer un paramètre s’appelle du passage par valeur ;
73
– La dernière possibilité consistant à réaliser tout passage de paramètre
par valeur semble irréaliste puisqu’elle ne permet pas de satisfaire le
besoin de modification d’un paramètre. C’est cependant la stratégie
choisie par les concepteurs du langage C.
74
void add(int a, int b, int *c)
/* c repère l’entier où on veut mettre le résultat */
{
*c = a + b;
}
int main(void)
{
int i,j,k;
3.11 Discussion
75
3.12 Une dernière précision
Quand un langage offre le passage de paramètre par valeur, il y a deux pos-
sibilités :
C’est la seconde solution qui a été retenue par les concepteurs du langage C.
Voyons sur un exemple. Supposons que l’on désire écrire une fonction sum
admettant comme paramètre n et qui rende la somme des n premiers entiers.
On peut programmer de la manière suivante :
int sum(int n)
{
int r = 0;
On voit que le paramètre n est utilisé comme variable locale, et que dans
l’instruction for, la partie initialisation est vide puisque n est initialisée par
l’appel de sum.
3.13 Exercice
On va coder un algorithme de cryptage très simple : on choisit un décalage
(par exemple 5), et un a sera remplacé par un f, un b par un g, un c par un
h, etc. On ne cryptera que les lettres majuscules et minuscules sans toucher
ni à la ponctuation ni à la mise en page (caractères blancs et line feed). On
supposera que les codes des lettres se suivent de a à z et de A à Z. On demande
de :
76
1. déclarer un tableau de caractères mess initialisé avec le message en clair ;
2. écrire une procédure crypt de cryptage d’un caractère qui sera passé
par adresse ;
77
#include <stdio.h>
#define DECALAGE 5
/**********************************************************************
/*
/* crypt
/*
/* But:
/* Crypte le caractère passé en paramètre
/*
/* Interface:
/* p : pointe le caractère à crypter
/*
/**********************************************************************
void crypt(char *p)
{
enum {BAS, HAUT};
int casse;
*p = *p + DECALAGE;
if (casse == BAS && *p > ’z’ || casse == HAUT && *p > ’Z’) *p = *p -26;
}
/**********************************************************************
/*
/* main
/*
/**********************************************************************
int main(void)
{
78
char *p;
int i;
/* phase de cryptage */
p = &mess[0];
while(*p)
crypt(p++);
/* impression du résultat */
printf("Résultat:\n");
printf(mess);
printf("\n");
return 0;
}
79
3.14 Lecture formattée
Il existe dans la bibliothèque standard une fonction de lecture formattée
qui fonctionne selon le même principe que la procédure printf. Sa syn-
taxe d’utilisation est la suivante :
scanf ( format , liste-d’expressions ) ;
format est une chaîne de caractères indiquant sous forme de séquences
d’échappement les entités que scanf lit sur l’entrée standard :
– %c pour un caractère.
scanf("%d %x",&i,&j);
Valeur rendue
Sur rencontre de fin de fichier, la fonction scanf rend EOF, sinon elle rend le
nombre de variables qu’elle a pu affecter.
80
Attention
Une erreur facile à commettre est d’omettre les opérateurs & devant les
paramètres de scanf. C’est une erreur difficile à détecter car le compilateur
ne donnera aucun message d’erreur et à l’exécution, ce sont les valeurs de i
et j qui seront interprétées comme des adresses par scanf. Avec un peu de
chance ces valeurs seront des adresses invalides, et le programme s’avortera2
sur l’exécution du scanf, ce qui donnera une idée du problème. Avec un peu
de malchance, ces valeurs donneront des adresses parfaitement acceptables,
et le scanf s’exécutera en allant écraser les valeurs d’autres variables qui ne
demandaient rien à personne. Le programme pourra s’avorter beaucoup plus
tard, rendant très difficile la détection de l’erreur.
• Syntaxe :
instruction :
⇒ switch ( expression )
{
case expression 1 : liste-d’instructions1 option
break;option
case expression 2 : liste-d’instructions2 option
break;option
....
2
Le terme avorter est à prendre au sens technique de abort
81
case expression n : liste-d’instructionsn option
break;option
default : liste-d’instructions
}
De plus :
• Sémantique :
Discussion
Vu le nombre de parties optionnelles dans la syntaxe, il y a 3 types
d’utilisations possibles pour le switch. Première possibilité, on peut avoir
dans chaque alternative une liste-d’instructions et un break; comme dans
l’exemple suivant :
3
la ou les, car dans chaque case, le break est optionnel
82
enum {BLEU=1, BLANC, ROUGE};
switch(c)
{
case ’0’:
case ’1’:
case ’2’:
case ’3’:
case ’4’:
case ’5’:
case ’6’:
case ’7’:
case ’8’:
case ’9’: nb_chiffres++; break;
default: nb_non_chiffres++;
}
83
enum {POSSIBLE, IMPOSSIBLE};
Remarque
Le mot-clé break est surchargé : nous avons vu au chapitre 2.2.4 que
l’instruction break permettait de stopper l’exécution d’une instruction itéra-
tive for, while, do. Il est utilisé ici de manière complètement différente.
• Sémantique :
Toute instruction peut être précédée d’un identificateur suivi du signe
:. Cet identificateur est appelé étiquette. Une instruction goto identi-
ficateur a pour effet de transférer le contrôle d’exécution à l’instruction
étiquetée par identificateur. L’instruction goto et l’instruction cible du
goto doivent se trouver dans la même procédure : le langage C est un
langage à branchement locaux.
84
• Exemple :
{
etiq2:
... /* des instructions */
goto etiq1; /* goto avant définition de l’étiquette */
... /* des instructions */
etiq1:
... /* des instructions */
goto etiq2; /* goto après définition de l’étiquette */
}
85
Attention
Cette instruction nulle peut parfois avoir des effets désastreux. Supposons
que l’on veuille écrire la boucle :
3.16 Exercice
Écrire une procédure main se comportant comme une calculette c’est à dire
exécutant une boucle sur :
3. imprimer le résultat.
Commentaire de la solution
Nous faisons ici un commentaire sur la solution proposée qui se trouve à
la page suivante. Dans le cas où la ligne lue n’a pas une syntaxe correcte
(elle ne contient pas un nombre, un signe, un nombre), le programme émet
un message d’erreur et exécute exit(1). Ceci ne réalise pas un interface
utilisateur bien agréable, car il serait plus intéressant de continuer la boucle
86
au lieu de terminer le programme. Cela n’a pas été implémenté car ce n’est
pas réalisable à l’aide des seules possibilités de base de scanf qui ont été
présentées. Dans le chapitre « Les entrées-sorties », scanf sera expliqué de
manière exhaustive et une meilleure version de ce programme sera présentée.
87
#include <stdio.h>
/**********************************************************************
/*
/* main
/*
/**********************************************************************
int main(void)
{
int i,j,r; /* les opérandes */
char c; /* l’opérateur */
char imp; /* booléen de demande d’impression du résultat */
int ret; /* code de retour de scanf */
while (1)
{
if ((ret = scanf("%d %c %d",&i,&c,&j)) != 3)
{
if (ret == EOF) exit(0);
printf("Erreur de syntaxe\n");
exit(1);
}
imp = VRAI;
switch (c)
{
case ’+’ : r = i + j; break;
case ’-’ : r = i - j; break;
case ’*’ : r = i * j; break;
case ’/’ :
if ( j == 0)
{
printf("Division par zéro\n");
imp = FAUX;
}
else r = i / j;
break;
88
case ’%’ : r = i % j; break;
default :
printf("l’opérateur %c est incorrect\n",c);
imp = FAUX;
} /* fin du switch */
if (imp) printf("%d\n",r);
}
return 0;
}
89
3.17 Récréation
Pour les amateurs de palindromes, voici la contribution de Brian Westley a
la compétition du code C le plus obscur (ioccc) de 1987.
char rahc
[ ]
=
"\n/"
,
redivider
[ ]
=
"Able was I ere I saw elbA"
,
*
deliver,reviled
=
1+1
,
niam ; main
( )
{/*\}
\*/
int tni
=
0x0
,
rahctup,putchar
( )
,LACEDx0 = 0xDECAL,
rof ; for
(;(int) (tni);)
(int) (tni)
= reviled ; deliver =
redivider
;
for ((int)(tni)++,++reviled;reviled* *deliver;deliver++,++(int)(tni)) r
=
90
(int) -1- (tni)
;reviled--;--deliver;
(tni) = (int)
- 0xDECAL + LACEDx0 -
rof ; for
(reviled--,(int)--(tni);(int) (tni);(int)--(tni),--deliver)
rahctup = putchar
(reviled* *deliver)
;
rahctup * putchar
((char) * (rahc))
;
/*\
{\*/}
Réponse de la devinette
Non, *i/*j n’est pas un quotient, car /* est un début de commentaire. Pour
obtenir le quotient de *i par *j il faut utiliser du blanc : *i / *j.
91
92
Chapter 4
int t[10];
Règle :
Tout identificateur de type « tableau de x » apparaissant dans une expression
est converti en une valeur constante dont :
93
Cette conversion n’a lieu que pour un identificateur de type « tableau de
x » apparaissant dans une expression. En particulier, elle n’a pas lieu
lors de la déclaration. Quand on déclare int T[10], le compilateur mémorise
que T est de type « tableau de 10 int » et réserve de la place en mémoire pour
10 entiers. C’est lors de toute utilisation ultérieure de l’identificateur T, que
cette occurrence de T sera convertie en type int *, de valeur adresse de T[0].
Remarques
1. La conversion automatique d’un identificateur ayant le type tableau
empêche de désigner un tableau en entier, c’est pour cette raison que
l’opérateur d’affectation ne peut affecter un tableau à un autre tableau :
int t1[10];
int t2[10];
t1 = t2; /* le compilateur rejettera cette instruction
int *p;
int t[10];
t = p; /* interdit */
p = t; /* valide */
L’existence des conversions sur les références aux tableaux va avoir deux con-
séquences importantes : la première concerne l’opérateur d’indexation et la
seconde le passage de tableaux en paramètre.
94
int t[N];
int i;
t[i] est équivalent à *(t + i). Vérifions que cela est bien conforme à la
façon dont nous l’avons utilisé jusqu’à présent. Nous avons vu que t à pour
valeur l’adresse du premier élément du tableau. D’après ce que nous savons
sur l’addition entre un pointeur et un entier, nous pouvons conclure que t + i
est l’adresse de l’élément de rang i du tableau. Si nous appliquons l’opérateur
d’indirection à (t+i) nous obtenons l’élément de rang i du tableau, ce que
nous notions jusqu’à présent t[i].
conséquence numéro 1
L’opérateur d’indexation noté [] est donc inutile, et n’a été offert que pour des
raisons de lisibilité des programmes, et pour ne pas rompre avec les habitudes
de programmation.
conséquence numéro 2
Puisque l’opérateur d’indexation s’applique à des valeurs de type pointeur,
on va pouvoir l’appliquer à n’importe quelle valeur de type pointeur, et pas
seulement aux constantes repérant des tableaux. En effet, après les déclara-
tions :
int t[10];
int * p;
on peut écrire :
p = &t[4];
et utiliser l’opérateur d’indexation sur p, p[0] étant t[4], p[1] étant t[5],
etc. p peut donc être utilisé comme un sous-tableau de t.
conséquence numéro 3
L’opérateur d’indexation est commutatif ! En effet, t[i] étant équivalent
à *(t + i) et l’addition étant commutative, t[i] est équivalent à *(i +
95
t) donc à i[t]. Lorsqu’on utilise l’opérateur d’indexation, on peut noter
indifféremment l’élément de rang i d’un tableau, t[i] ou i[t]. Il est bien
évident que pour des raisons de lisibilité, une telle notation doit être prohibée,
et doit être considérée comme une conséquence pathologique de la définition
de l’opérateur d’indexation.
96
... /* corps de la procédure proc */
}
Cette façon d’exprimer les choses est beaucoup plus claire, et sera donc
préférée. L’appel se fera de la manière suivante :
97
#define NB_ELEM 10
int tab[NB_ELEM];
int main(void)
{
imp_tab(tab,NB_ELEM);
}
Remarque
Quand une fonction admet un paramètre de type tableau, il y a deux cas
possibles :
– soit les différents tableaux qui lui sont passés en paramètre effectif ont
des tailles différentes, et dans ce cas la taille doit être un paramètre
supplémentaire de la fonction, comme dans l’exemple précédent ;
– soit les différents tableaux qui lui sont passés en paramètre effectif ont
tous la même taille, et dans ce cas la taille peut apparaître dans le type
du paramètre effectif :
#define NB_ELEM 10
void imp_tab(int t[NB_ELEM])
{
...
}
98
Il semble donc que le passage de tableau en paramètre se fasse par adresse
et non par valeur et qu’il s’agisse d’une exception à la règle qui affirme qu’en
C, tout passage de paramètre se fait par valeur. Mais il n’en est rien : c’est
la conversion automatique des identificateurs de type tableau qui provoque
ce phénomène.
Du point de vue pratique, on retiendra que l’on peut modifier les éléments
d’un tableau passé en paramètre. On peut écrire par exemple :
qui déclare une variable de nom i dont il est interdit de modifier la valeur.
L’intérêt de const se manifeste pour les paramètres de fonction. Reprenons
l’exemple de la procédure imp_tab, pour exprimer le fait que cette procédure
ne doit pas modifier les éléments du tableau t, on peut (et il est recommandé
de) l’écrire de la façon suivante :
99
int i;
printf("Bonjour");
Règle
Lorsque les chaînes littérales apparaissent dans un autre contexte qu’une déc-
laration avec initialisation de tableau de caractères, elles subissent une con-
version en pointeur vers char. Si une fonction a comme paramètre formel un
tableau de caractères, on pourra lui passer en paramètre effectif aussi bien le
nom d’un tableau de caractères qu’une chaîne littérale. Exemple :
100
4.7 Retour sur printf
Nous avons vu au paragraphe 1.16 que la fonction printf admet comme
premier paramètre une chaîne à imprimer comportant éventuellement des
séquence d’échappement : %d, %o et %x pour imprimer un nombre respective-
ment en décimal, octal et hexadécimal et %c pour imprimer un caractère. Il
existe aussi une séquence d’échappement pour imprimer les chaînes de carac-
tères :%s. Exemple :
4.8 Exercice
1. Déclarer et initialiser deux tableaux de caractères (ch1 et ch2).
101
#include <stdio.h>
/**********************************************************************
/*
/* lg_chaine1
/*
/* But:
/* calcule la longueur d’une chaîne de caractères
/*
/* Interface:
/* ch : la chaîne de caractères
/* valeur rendue : la longueur de ch
/*
/**********************************************************************
int lg_chaine1(const char ch[])
{
int i = 0;
return(i);
}
/**********************************************************************
/*
/* lg_chaine2
/*
/* But:
/* identique à celui de lg_chaine1
/*
/**********************************************************************
int lg_chaine2(const char *ch)
{
int i = 0;
102
while (*ch != NULL_C)
{ i++; ch++; }
return(i);
}
/**********************************************************************
/* main
/**********************************************************************
int main(void)
{
printf("la longeur de ch1 est %d\n",lg_chaine1(ch1));
printf("la longeur de ch2 est %d\n",lg_chaine2(ch2));
return 0;
}
103
4.9 Tableaux multidimensionnels
4.9.1 Déclarations
En C, un tableau multidimensionnel est considéré comme étant un tableau
dont les éléments sont eux mêmes des tableaux. Un tableau à deux dimensions
se déclare donc de la manière suivante :
int t[10][20];
Les mêmes considérations que celles que nous avons développées sur les
tableaux à une dimension s’appliquent, à savoir :
#define N 10
p(int t[][N])
{
... /* corps de p */
}
104
On peut en effet omettre la taille de la première dimension, mais il est néces-
saire d’indiquer la taille de la seconde dimension, car le compilateur en a
besoin pour générer le code permettant d’accéder à un élément. En effet,
si T est la taille des éléments de t, l’adresse de t[i][j] est : adresse de
t + (i × N + j) × T . Le compilateur à besoin de connaître N, ce sera donc une
constante. Par contre, la taille de la première dimension pourra être passée en
paramètre, comme nous l’avons fait pour les tableaux à une seule dimension.
Exemple :
#define P 10
4.10 Initialisation
Un tableau multidimensionnel peut être initialisé à l’aide d’une liste de listes
d’expressions constantes. Exemple :
int t[4][5] = {
{ 0,1,2,3,4},
{ 10,11,12,13,14},
{ 20,21,22,23,24},
{ 30,31,32,33,34},
};
Un telle initialisation doit se faire avec des expressions constantes, c’est à dire
délivrant une valeur connue à la compilation.
105
4.11 Exercice
1. Déclarer et initialiser statiquement une matrice [5,5] d’entiers (tab).
106
#include <stdio.h>
#define N 5
int tab[N][N] =
{
{0,1,2,3,4},
{10,11,12,13,14},
{20,21,22,23,24},
{30,31,32,33,34},
{40,41,42,43,44}
};
/**********************************************************************
/*
/* print_mat
/*
/* But:
/* Imprime un tableau N sur N (N est une constante)
/*
/**********************************************************************
print_mat(int t[][N])
{
int i,j;
/**********************************************************************
/*
/* main
/*
/**********************************************************************
107
int main(void)
{
print_mat(tab);
return 0;
}
108
4.12 Tableau de pointeurs
4.12.1 Cas général
Pour des raisons de gain de place mémoire, on est parfois amené à créer des
tableaux à deux dimensions dont toutes les lignes n’ont pas la même taille.
Ceci peut se réaliser à l’aide d’un tableau de pointeurs vers des tableaux de
tailles différentes, associé à un autre tableau qui donnera la taille de chaque
ligne. On obtient alors la structure de données suivante :
taille tab
Si nous supposons que le type des objets terminaux est int, pour traduire
cette structure de données en langage C, les programmeurs ont l’habitude
de « tricher », et de ne pas utiliser le type tableau de pointeurs vers des
tableaux de int, considéré comme étant trop compliqué (un tel type s’écrit :
int (*tab[NB_ELEM])[]). La solution habituellement retenue, consiste à
utiliser le type tableau de pointeurs vers des int, soit int *tab[NB_ELEM].
Voici un exemple d’une telle déclaration avec initialisation statique du
tableau :
#define NB_ELEM 3
int taille[NB_ELEM] = {1, 2, 3};
int ligne1[] = {10};
int ligne2[] = {20,21};
109
int ligne3[] = {30,31,32};
Première méthode
On utilise un pointeur vers un entier que l’on fait progresser d’élément en
élément dans une ligne :
110
int i, *p;
for (i = 0; i < NB_ELEM; i++)
{
for (p = tab[i]; p < tab[i] + taille[i]; p++)
printf("%d ",*p); /* accès à l’élément courant par *p */
printf("\n");
}
Deuxième méthode
On utilise un pointeur vers le premier élément d’une ligne ; ce pointeur reste
fixe.
int i, j, *p;
for (i = 0; i < NB_ELEM; i++)
{
for (p = tab[i], j = 0; j < taille[i]; j++)
printf("%d ",p[j]); /* accès à l’élément courant par p[j]
printf("\n");
}
Troisième méthode
La dernière méthode est surprenante : pour la comprendre, il suffit de remar-
quer que la variable p dans la seconde solution est inutile, on peut accéder à
l’élément courant par la notation tab[i][j].
int i, j, *p;
for (i = 0; i < NB_ELEM; i++)
{
for (j = 0; j < taille[i]; j++)
/* accès à l’élément courant par tab[i][j] */
printf("%d ", tab[i][j]);
printf("\n");
}
111
On remarquera que c’est la même notation que celle qui est utilisée quand
on a un vrai tableau à deux dimensions, c’est à dire une structure de don-
nées physiquement complètement différente. Que l’accès à deux structures de
données différentes puissent se réaliser de la même manière, doit sans doute
être considéré comme une faiblesse du langage.
char * t[NB_ELEM];
On remarquera que ceci est impossible avec tout autre type que les char : il
est impossible d’écrire :
#define NBMOIS 12
int i;
112
4.12.3 Paramètres d’un programme
Les tableaux de pointeurs vers des chaînes de caractères sont une structure
de données importante, car c’est sur celle-ci que s’appuie la transmission
de paramètres lors de l’exécution d’un programme. Lorsqu’un utilisateur
lance l’exécution du programme prog avec les paramètres param1, param2,
... paramn, l’interpréteur de commandes collecte tous ces mots sous forme de
chaînes de caractères, crée un tableau de pointeurs vers ces chaînes, et lance
la procédure main en lui passant deux paramètres :
Les noms argc (pour argument count), ainsi que argv (pour argument values),
sont des noms traditionnels, mais peuvent être remplacés par n’importe quels
autres noms ; seuls les types doivent être respectés.
Comme exemple d’utilisation des paramètres, nous donnons le source d’un
programme qui imprime son nom et ses paramètres :
113
4.13 Tableau et pointeur, c’est la même
chose ?
Il y a un moment dans l’étude du langage C, où on a l’impression que les
tableaux et les pointeurs sont plus ou moins interchangeables, en un mot que
c’est pratiquement la même chose. il faut donc être très clair : un tableau
et un pointeur ce n’est pas la même chose. Quand on déclare, ailleurs
qu’en paramètre formel de fonction, int t[10]; on déclare un tableau, et
le compilateur réserve une zone de 10 entiers consécutifs. Quand on déclare
int *p, il s’agit toujours d’un pointeur, et le compilateur réserve simplement
un élément de mémoire pouvant contenir un pointeur.
Les caractéristiques du langage C qui créent la confusion dans l’esprit des
utilisateurs, sont les trois règles suivantes :
4.13.1 Commentaires
Bien noter les points suivants :
114
• Différence entre règle 1 et règle 2 : une déclaration int t[10] qui n’est
pas un paramètre formel, déclare un tableau de 10 entiers. Ce sont
les utilisations ultérieures de t qui subissent une conversion en type
pointeur vers entier. Par contre, à la déclaration d’un paramètre formel
int t[], c’est la déclaration elle-même qui est transformée en int *t.
char *p = "Hello"; p: H e l
115
Ceci est un cas particulier des tableaux de caractères qui ne se repro-
duit pas avec les autres types. On peut déclarer un tableau d’entiers par
int t[] = {1, 2, 3, 4, 5};, mais on ne peut pas déclarer un pointeur
vers un tableau d’entiers par :
int *p = {1, 2, 3, 4, 5};.
4.14 Récréation
En illustration sur les bizarreries des tableaux dans le langage C, voici la
contribution de David Korn (le créateur du korn shell) à la compétition du
code C le plus obscur (ioccc) de 1987 :
main() { printf(&unix["\021%six\012\0"],(unix)["have"]+"fun"-0x60)
Non, ce programme n’imprime pas have fun with unix ou quelque chose
de ce genre ! Le lecteur est invité à essayer d’élucider ce programme (oui, il
imprime quelque chose, mais quoi ?) la solution est donnée à la page suivante.
116
Voici les clés de la compréhension :
1. Tout compilateur C possède un certain nombre de constantes prédéfinies
dépendant de l’architecture de la machine sur laquelle il s’exécute. En
particulier, tout compilateur pour machine unix prédéfinit la constante
unix avec comme valeur 1. Donc le programme est équivalent à :
main() { printf(&1["\021%six\012\0"],(1)["have"]+"fun"-0x60);}
3. La fin d’une chaîne est signalée par un caractère null (\0) et le compi-
lateur en met un à la fin de chaque chaîne littérale. Celui qui est ici est
donc inutile. D’autre part, il existe une notation plus parlante pour le
caractère de code \012 (c’est à dire new line), il s’agit de la notation
\n. Le programme peut donc se réécrire :
4. Le caractère ’a’ a pour code ascii 0x61, donc ’a’ -0x60 est égal à 1.
Réécrivons le programme :
main() { printf("%six\n","un"); }
117
118
Chapter 5
Les entrées-sorties
119
Utilisation
fopen (nom-de-fichier , mode)
– mode est de type pointeur vers char. La chaîne pointée indique le mode
d’ouverture, elle peut être l’une des chaîne suivantes :
Remarque
120
Valeur rendue
La fonction fopen retourne une valeur de type pointeur vers FILE, où FILE
est un type prédéfini dans le fichier stdio.h.
121
stdio.h de type pointeur vers FILE qui repèrent ces trois fichiers. Elles ont
pour nom respectivement stdin, stdout et stderr.
#include <stdio.h>
FILE *fp;
Utilisation
fclose (flot-de-données)
Valeur rendue
La fonction fclose rend la valeur zéro si le fichier a été fermé, et rend EOF si
il y a eu une erreur.
122
Utilisation typique
#include <stdio.h>
FILE *f;
fclose(f);
Description
La fonction fgetc lit un caractère du fichier flot-de-données.
Valeur rendue
Si la lecture se fait sans erreur et sans rencontre de la fin de fichier, fgetc
rend le caractère lu. Si il y a erreur d’entrée-sortie, ou rencontre de fin de
fichier, fgetc rend la valeur EOF. Pour cette raison, le type de la valeur rendue
est int et non pas char.
Utilisation typique
#include <stdio.h>
int c;
123
FILE *fi;
int i;
FILE * TAB_FILE[10];
getchar ( )
Description
124
5.3.4 Écriture par caractère : fputc
Utilisation
Description
Valeur rendue
La fonction fputc rend le caractère écrit si l’écriture s’est faite sans erreur,
ou EOF en cas d’erreur.
Utilisation typique
#include <stdio.h>
int c;
FILE *fi,*fo;
125
5.3.5 Écriture par caractère : putc
Il existe une fonction putc qui est rigoureusement identique à fputc (même
interface, même sémantique), sauf que putc est implémenté comme une macro
et non comme une vraie fonction C. La même remarque que celle faite au sujet
de getc s’applique donc à putc.
Description
Un appel putchar(c) est rigoureusement identique à fputc(c,stdout).
126
Valeur rendue
Attention
Sur fin de fichier ou erreur, fgets rend NULL et non pas EOF.
Grrr ...
Description
La fonction fgets lit les caractères du fichier et les range dans le tableau
pointé par chaîne jusqu’à rencontre d’un line-feed (qui est mis dans le
tableau), ou rencontre de fin de fichier, ou jusqu’à ce qu’il ne reste plus qu’un
seul caractère libre dans le tableau. fgets complète alors les caractères lus
par un caractère null.
Utilisation typique
#include <stdio.h>
#define LONG ...
char ligne[LONG];
FILE *fi;
gets (chaîne)
127
Sémantique des paramètres
– chaîne est de type pointeur vers char et doit pointer vers un tableau
de caractères.
Valeur rendue
Description
La fonction gets est un fgets sur stdin avec la différence que le line feed
n’est pas mis dans chaîne. Malheureusement, l’interface de gets est une
catastrophe : il n’a pas le paramètre taille qui donne la taille du tableau
pointé par chaîne. Ceci interdit donc à gets toute vérification pour ne pas
déborder du tableau.
Pour cette raison l’usage de gets est très fortement déconseillé 1
– chaîne est de type pointeur vers char. Pointe vers un tableau de car-
actères contenant une chaîne se terminant par un null.
128
Valeur rendue
La fonction fputs rend une valeur non négative si l’écriture se passe sans
erreur, et EOF en cas d’erreur.
Description
fputs écrit sur le fichier le contenu du tableau dont la fin est indiquée par
un caractère null. Le tableau de caractères peut contenir ou non un line-
feed. fputs peut donc servir indifféremment à écrire une ligne ou une chaîne
quelconque.
Utilisation typique
#include <stdio.h>
#define LONG ...
char ligne[LONG];
FILE *fo;
fputs(ligne,fo);
Valeur rendue
La fonction fputs rend une valeur non négative si l’écriture se passe sans
erreur, et EOF en cas d’erreur.
129
Description
La fonction puts est un fputs sur stdout. Elle n’est pas entaché du même
vice rédhibitoire que gets, on peut donc l’utiliser.
– format est une chaîne de caractères qui spécifie ce qui doit être écrit.
Valeur rendue
La fonction fprintf retourne le nombre de caractères écrits, ou une valeur
négative si il y a eu une erreur d’entrée-sortie.
Présentation
La chaîne format contient des caractères ordinaires (c’est à dire différents du
caractère %) qui doivent être copiés tels quels, et des séquences d’échappement
(introduites par le caractère %), décrivant la manière dont doivent être écrits
les paramètres param 1 , param 2 , ... param n .
Si il y a moins de param i que n’en réclame le format, le comportement
n’est pas défini. Si il y a davantage de param i que n’en nécessite le format,
les param i en excès sont évalués, mais leur valeur est ignorée.
130
Les séquences d’échappement
Une séquence d’échappement se compose des éléments suivants :
131
un nombre de caractères inférieur à cette taille, param i est complété
à droite ou à gauche (selon que l’on aura utilisé ou pas l’indicateur
-), avec des blancs ou des 0, comme il a été expliqué plus haut.
132
h s’appliquant au format n : param i sera interprété comme
un pointeur vers un short int.
2
l . s’appliquant aux formats d, i, o, u, x ou X : param i
sera interprété comme un long int ou un unsigned long
int.
3
l s’appliquant au format n : param i sera interprété comme
un pointeur vers un long int.
L s’appliquant aux formats e, E, f, g ou G : param i sera
interprété comme un long double.
133
s param i sera interprété comme l’adresse d’un tableau de
caractères (terminé ou non par un null). Les caractères
du tableau seront imprimés jusqu’au premier des deux
évènements possibles :
– impression de précision caractères de param i .
– rencontre de null dans param i .
Dans le cas où param i n’est pas terminé par un null, le
format d’impression doit comporter une indication de pré-
cision.
p param i sera interprété comme un pointeur vers void.
Le pointeur sera imprimé sous une forme dépendante de
l’implémentation.
n param i sera interprété comme un pointeur vers un
int auquel sera affecté le nombre de caractères écrits
jusqu’alors par cette invocation de fprintf.
e,E param i sera interprété comme un double et écrit sous la
forme :
-option pe . pf e signe exposant
dans laquelle pe et pf sont respectivement partie entière et
partie fractionnaire de la mantisse. La partie entière est
exprimée avec un seul chiffre, la partie fractionnaire est
exprimée avec un nombre de chiffres égal à la précision.
La précision est prise égale à 6 par défaut. Si la précision
est 0, le point décimal n’apparaît pas. L’exposant con-
tient toujours au moins deux chiffres. Si param i est nul,
l’exposant sera nul. Dans le cas du format E, la lettre E
est imprimée à la place de e.
f param i sera interprété comme un double et écrit sous la
forme :
-option pe . pf
dans laquelle pe et pf sont respectivement partie entière et
partie fractionnaire de la mantisse. La partie fractionnaire
est exprimée avec un nombre de chiffres égal à la précision.
134
La précision est prise égale à 6 par défaut. Si la précision
est 0, le point décimal n’apparaît pas.
g,G param i sera interprété comme un double et écrit sous le
format f, ou le format e, selon sa valeur. Si param i a un
exposant inférieur à -4 ou plus grand ou égal à la précision,
il sera imprimé sous le format e, sinon il sera imprimé sous
le format f. D’éventuels zéros à la fin de la partie frac-
tionnaire sont enlevés. Le point décimal n’apparaît que
si il est suivi d’au moins un chiffre. Dans ce qui précède,
l’utilisation du format G implique l’utilisation du format
E à la place du format e.
% Un caractère % est écrit.
Utilisation
La fonction printf admet un nombre variable de paramètres. Son utilisation
est la suivante :
printf ( format , param 1 , param 2 , ... , param n )
Description
Un appel printf(fmt, ...) est rigoureusement identique à
fprintf(stdout,fmt,...).
135
Description
La fonction sprintf réalise le même traitement que la fonction fprintf,
avec la différence que les caractères émis par sprintf ne sont pas écrits dans
un fichier, mais dans le tableau de caractères chaîne. Un null est écrit dans
chaîne en fin de traitement.
136
5.5.4 Exemples d’utilisation des formats
source C resultat
printf("|%d|\n",1234); |1234|
printf("|%d|\n",-1234); |-1234|
printf("|%+d|\n",1234); |+1234|
printf("|%+d|\n",-1234); |-1234|
printf("|% d|\n",1234); | 1234|
printf("|% d|\n",-1234); |-1234|
printf("|%x|\n",0x56ab); |56ab|
printf("|%X|\n",0x56ab); |56AB|
printf("|%#x|\n",0x56ab); |0x56ab|
printf("|%#X|\n",0x56ab); |0X56AB|
printf("|%o|\n",1234); |2322|
printf("|%#o|\n",1234); |02322|
printf("|%10d|\n",1234); | 1234|
printf("|%10.6d|\n",1234); | 001234|
printf("|%.6d|\n",1234); |001234|
printf("|%*.6d|\n",10,1234); | 001234|
printf("|%*.*d|\n",10,6,1234); | 001234|
printf("|%f|\n",1.234567890123456789e5); |123456.789012|
printf("|%.4f|\n",1.234567890123456789e5); |123456.7890|
printf("|%.20f|\n",1.234567890123456789e5); |123456.78901234567456413060|
printf("|%20.4f|\n",1.234567890123456789e5); | 123456.7890|
printf("|%e|\n",1.234567890123456789e5); |1.234568e+05|
printf("|%.4e|\n",1.234567890123456789e5); |1.2346e+05|
printf("|%.20e|\n",1.234567890123456789e5); |1.23456789012345674564e+05|
printf("|%20.4e|\n",1.234567890123456789e5); | 1.2346e+05|
printf("|%.4g|\n",1.234567890123456789e-5); |1.235e-05|
printf("|%.4g|\n",1.234567890123456789e5); |1.235e+05|
printf("|%.4g|\n",1.234567890123456789e-3); |0.001235|
printf("|%.8g|\n",1.234567890123456789e5); |123456.79|
137
5.5.5 Entrées formattées : fscanf
Utilisation
La fonction fscanf admet un nombre variable de paramètres. Son utilisation
est la suivante :
fscanf ( flot-de-données , format , param 1 , param 2 , ... , param n )
– les param i sont des pointeurs. Ils pointent des variables dans lesquelles
fscanf dépose les valeurs lues dans flot-de-données, après les avoir con-
verties en binaire.
Valeur rendue
Si au moins un parami s’est vu affecter une valeur, fscanf retourne le nombre
de parami affectés. Si il y a eu rencontre de fin de fichier ou erreur d’entrée-
sortie avant toute affectation à un parami , fscanf retourne EOF.
Description
fscanf lit une suite de caractères du fichier défini par flot-de-données en
vérifiant que cette suite est conforme à la description qui en est faite dans
format. Cette vérification s’accompagne d’un effet de bord qui consiste à
affecter des valeurs aux variables pointées par les différents param i .
Quelques définitions
flot d’entrée il s’agit de la suite de caractères lus du fichier défini par flot-
de-données.
138
caractères blancs il s’agit des six caractères suivants : espace, tab, line feed,
new line, vertical tab et form feed.
modèle un modèle est la description d’un ensemble de chaînes de caractères.
Exemple : %d est le modèle des chaînes formées de chiffres décimaux,
éventuellement signées.
conforme on dira qu’une chaîne est conforme à un modèle quand elle ap-
partient à l’ensemble des chaînes décrites par le modèle. Exemple : 123
est conforme au modèle %d.
directive une directive peut être :
– une suite de caractères blancs qui est un modèle d’un nombre quel-
conque de caractères blancs. Exemple : un espace est un modèle
pour un nombre quelconque d’espaces, ou d’un nombre quelconque
d’espace et de tab mélangés, ou d’un nombre quelconque d’espaces,
de tab et de line-feed mélangés etc.
– une suite de caractères ordinaires (c’est à dire qui ne sont ni des
caractères blancs, ni le caractère %) qui est un modèle pour elle-
même. Exemple : la chaîne hello est un modèle de la seule chaîne
hello.
– des séquences d’échappement introduites par le caractère %. Ces
séquences jouent un double rôle : elle sont à la fois un modèle des
chaînes acceptables dans le flot d’entrée, et elles sont également des
ordres de conversion de la chaîne lue et d’affectation du résultat
à une variable pointée par le parami correspondant. Exemple :
la directive %d est un modèle des nombres décimaux et un ordre
de conversion de la chaîne lue en valeur binaire et d’affectation à
l’entier pointé par le parami correspondant.
139
2 un nombre (optionnel) qui indique la longueur maximum de la chaîne
acceptable du flot d’entrée.
140
Le parami correspondant est interprété comme un poin-
teur vers un int, sauf si la directive contient un modifi-
cateur de type.
o modèle pour un nombre octal éventuellement précédé d’un
signe. Le parami correspondant est interprété comme un
pointeur vers un unsigned int, sauf si la directive con-
tient un modificateur de type.
u modèle pour un nombre décimal éventuellement précédé
d’un signe.4 Le parami correspondant est interprété
comme un pointeur vers un unsigned int, sauf si la di-
rective contient un modificateur de type.
x modèle pour un nombre hexadécimal éventuellement
précédé d’un signe. Le parami correspondant est inter-
prété comme un pointeur vers un unsigned int, sauf si
la directive contient un modificateur de type.
Remarque
141
s modèle pour une suite de caractères non blancs. Le parami
correspondant est interprété comme un pointeur vers un
tableau de caractères suffisamment grand pour contenir
la chaîne lue plus un null terminal.
e,f,g modèle pour un flottant écrit selon la syntaxe d’une con-
stante flottante du langage C. Le parami correspondant
est interprété comme un pointeur vers un float , sauf si
la directive contient un modificateur de type.
[ Dans la chaîne format, ce caractère introduit une séquence
particulière destinée à définir un scanset. La séquence
est formée du caractère [, suivi d’une suite de caractères
quelconques, suivi du caractère ]. Si le premier caractère
après le crochet ouvrant n’est pas le caractère ^, le scanset
est l’ensemble des caractères entre crochets. Si le carac-
tère après le crochet ouvrant est le caractère ^, le scanset
est l’ensemble des caractères ne se trouvant pas dans la
chaîne entre crochets. Le scanset peut comprendre le car-
actère ] à condition de le mettre en début soit [] ...]
ou [^]...] selon que l’on utilise la forme sans ou avec ^.
Le scanset peut contenir ^ à condition de ne pas le mettre
en tête : [...^...].
Une directive [ est un modèle pour une suite de caractères
appartenant au scanset. Le parami correspondant est in-
terprété comme un pointeur vers un tableau de caractères
suffisamment grand pour contenir la chaîne lue plus un
null terminal.
p modèle pour un pointeur écrit d’une manière dépendant
de l’implémentation, mais identique à l’impression par
printf d’un pointeur selon le format %p. Le parami corre-
spondant est interprété comme un pointeur vers un poin-
teur vers void.
n cette directive n’est pas un modèle. Elle ne sert qu’à met-
tre une valeur dans l’objet pointé par le parami correspon-
dant. Le parami correspondant est interprété comme un
pointeur vers un int dans lequel fscanf écrit le nombre
142
de caractères lus jusqu’à ce moment, dans le flot de don-
nées, par cette invocation de fscanf. L’exécution d’une
directive %n n’augmente pas le nombre des parami affectés
qui sera retourné par fscanf (Cf 5.5.5).
% est un modèle pour le caractère %. La directive complète
est %%.
Algorithme de fscanf
La chaîne format doit se composer d’un ensemble de directives. Il doit y avoir
autant de parami que de directives demandant l’affectation d’une valeur. Si il
n’y a pas suffisamment de parami pour le format, le comportement n’est pas
défini. Si il y a davantage de parami que demandé par le format, les parami
en excès sont évalués mais ils sont inutilisés.
La fonction fscanf exécute dans l’ordre chaque directive du format. Si
une directive échoue, la fonction fscanf retourne à l’appelant.
143
3. si la directive ne contient pas le caractère *, convertir la chaîne lue
et l’affecter à l’objet pointé par le parami correspondant. Si cet
objet n’est pas de la taille ou du type convenable pour la recevoir,
le comportement n’est pas défini.
144
5.5.6 Entrées formattées : scanf
Nous avons déjà vu scanf, nous allons la définir ici formellement.
Utilisation
La fonction scanf admet un nombre variable de paramètres. Son utilisation
est la suivante :
scanf ( format , param 1 , param 2 , ... , param n )
Description
Un appel scanf(fmt,...) est rigoureusement identique à
fscanf(stdin,fmt,...).
Description
La fonction sscanf réalise le même traitement que la fonction fscanf, avec la
différence que les caractères lus par sscanf ne sont pas lus depuis un fichier,
mais du tableau de caractères chaîne. La rencontre du null terminal de chaîne
pour sscanf est équivalent à la rencontre de fin de fichier pour fscanf.
5.6 Récréation
En illustration du printf et en guise de récréation, je propose un programme
dont l’exécution imprime le source du programme. Ce n’est pas facile du tout
de créer un tel programme si on exclut la version triviale consistant à faire
un open sur le source, le lire et l’imprimer. Voici une solution possible que
l’on trouve dans le source du compilateur gnu c :
145
main(){char*p="main(){char*p=%c%s%c;printf(p,34,p,34,10);}%c";prin
5.7 Exercice 1
Soit un fichier de données structuré en une suite de lignes contenant chacune
un nom de personne, un nom de pièce, un nombre et un prix. Exemple :
dupond vilebrequin 10 1000
écrire une procédure main dans laquelle on déclarera les variables suivantes :
– la lecture d’une ligne se fera par un appel à scanf affectant les 4 champs
de la ligne aux 4 variables nom, article, nombre et prix.
5.8 Exercice 2
Reprendre la calculette réalisée en fin de chapitre sur les pointeurs et y ra-
jouter une gestion correcte des erreurs. Si l’utilisateur tape une ligne incor-
recte, on désire l’émission d’un message d’erreur, et une continuation de la
boucle de calcul.
146
#include "stdio.h"
/**********************************************************************
/*
/* main
/*
/**********************************************************************
int main(void)
{
FILE * fi;
char nom[80];
char article[80];
int nombre,prix;
return 0;
}
147
#include <stdio.h>
/**********************************************************************
/*
/* main
/*
/**********************************************************************
int main(void)
{
int i,j,r; /* les opérandes */
char c; /* l’opérateur */
char imp; /* booléen de demande d’impression du résultat */
int ret; /* code de retour de scanf */
char buf_err[80];
while (1)
{
if ((ret = scanf("%d %c %d",&i,&c,&j)) != 3)
{
if (ret == EOF) exit(0);
scanf("%[^\n]",buf_err); /* on mange la partie erronée */
printf("Erreur de syntaxe : %s\n",buf_err);
continue;
}
imp = VRAI;
switch (c)
{
case ’+’ : r = i + j; break;
case ’-’ : r = i - j; break;
case ’*’ : r = i * j; break;
case ’/’ :
if ( j == 0)
{
printf("Division par zéro\n");
imp = FAUX;
}
148
else r = i / j;
break;
case ’%’ : r = i % j; break;
default :
printf("l’opérateur %c est incorrect\n",c);
imp = FAUX;
} /* fin du switch */
if (imp) printf("%d\n",r);
}
return 0;
}
149
150
Chapter 6
Structures, unions et
énumérations
151
Première méthode
La déclaration :
struct personne
{
char nom[20];
char prenom[20];
int no_employe;
};
Deuxième méthode
On peut déclarer des variables de type structure sans utiliser d’étiquette de
structure, par exemple :
struct
{
char nom[20];
char prenom[20];
int no_employe;
} p1,p2;
152
struct
{
char nom[20];
char prenom[20];
int no_employe;
} p3;
les deux structures ont beau avoir le même nombre de champs, avec les mêmes
noms et les mêmes types, elles seront considérées de types différents. Il sera
impossible en particulier d’écrire p3 = p1;.
Troisième méthode
On peut combiner déclaration d’étiquette de structure et déclaration de vari-
ables, comme ceci :
struct personne
{
char nom[20];
char prenom[20];
int no_employe;
} p1,p2;
déclare les deux variables p1 et p2 et donne le nom personne à la structure.
Là aussi, on pourra utiliser ultérieurement le nom struct personne pour
déclarer d’autres variables :
struct personne pers1, pers2, pers3;
qui seront du même type que p1 et p2.
De ces trois méthodes c’est la première qui est recommandée, car elle
permet de bien séparer la définition du type structure de ses utilisations.
153
6.3 Opérateurs sur les structures
6.3.1 Accès aux membres des structures
Pour désigner un membre d’une structure, il faut utiliser l’opérateur de sélec-
tion de membre qui se note . (point). Par exemple, si p1 et p2 sont deux
variables de type struct personne, on désignera le membre nom de p1 par
p1.nom et on désignera le membre no_employe de p2 par p2.no_employe.
Les membres ainsi désignés se comportent comme n’importe quelle variable
et par exemple, pour accéder au premier caractère du nom de p2, on écrira :
p2.nom[0].
154
6.5 Exercice
Soit un fichier de données identiques à celui de l’exercice précédent.
Écrire une procédure main qui :
155
#include <stdio.h>
/******************************************************/
/* main */
/******************************************************/
int main(void)
{
FILE * fi;
struct commande
{
char nom[80];
char article[80];
int nombre,prix;
};
if (i >= nb_com)
printf("le tableau tab_com est sous-dimentionné\n");
156
else
{
/* impression des commandes mémorisées */
/* ----------------------------------- */
ilast = i - 1;
fclose(fi);
}
}
return 0;
}
157
6.6 Pointeurs vers une structure
Supposons que l’on ait défini la struct personne à l’aide de la déclaration :
struct personne
{
...
};
struct personne
{
...
};
int main(void)
{
struct personne pers; /* pers est une variable de type struct
struct personne *p; /* p est un pointeur vers une struct per
p = &pers;
}
158
struct personne
{
... /* les différents membres */
struct personne *suivant;
};
le membre de nom suivant est déclaré comme étant du type pointeur vers une
struct personne. La dernière structure de la liste devra avoir un membre
suivant dont la valeur sera le pointeur NULL que nous avons vu en 5.1
159
struct date
{
int jour,mois,annee;
};
une fonction de comparaison de deux dates pourra s’écrire :
enum {AVANT, EGAL, APRES};
if (cmp_date(d1,d2) == AVANT)
...
Attention
En langage C k&r, il n’est pas possible de passer en paramètre une structure,
mais on peut passer un pointeur vers une structure.
160
int i,taille;
taille = sizeof i;
taille = sizeof (short int);
taille = sizeof (struct personne);
int t[10];
161
Allocation d’un élément : fonction malloc
La fonction malloc admet un paramètre qui est la taille en octets de l’élément
désiré et elle rend un pointeur vers l’espace alloué. Utilisation typique :
#include <stdlib.h>
struct personne *p;
p = malloc(sizeof(struct personne));
son but est d’allouer un espace suffisant pour contenir les éléments demandés
et de rendre un pointeur vers cet espace.
162
Utilisation typique :
#include <stdlib.h>
struct personne *p;
int nb_elem;
p = malloc(sizeof(struct personne));
... /* utilisation de la structure allouée */
free(p);
6.12 Exercice
Modifier le programme précédent :
1. en écrivant une procédure d’impression d’une struct commande passée
en paramètre.
2. en écrivant une fonction de recherche de commande maximum (celle
pour laquelle le produit nombre × prix est maximum). Cette fonction
admettra en paramètre un pointeur vers la struct commande qui est
tête de la liste complète, et rendra un pointeur vers la structure recher-
chée.
3. le main sera modifié de manière à faire appel à la fonction de recherche
de la commande maximum et à imprimer cette commande.
163
#include <stdlib.h>
#include <stdio.h>
/*********************************************************************/
/* */
/* print_com */
/* */
/* But: */
/* Imprime une structure commande */
/* */
/*********************************************************************/
void print_com(struct commande com)
{
printf("%s %s %d %d\n",
com.nom, com.article, com.nombre, com.prix);
}
164
/*********************************************************************/
/* */
/* max_com */
/* */
/* But: */
/* Recherche la commande pour laquelle le produit nombre * prix */
/* est le maximum */
/* */
/* Interface: */
/* l_com : la liste dans laquelle doit se faire la recherche */
/* valeur rendue : pointeur vers la structure commande recherchée */
/* ou NULL si l_com est vide */
/* */
/*********************************************************************/
struct commande *max_com(struct commande * l_com)
{
struct commande *pmax; /* pointeur vers le max courant */
struct commande *pcour; /* pointeur vers l’element courant */
int vmax,vcour;
if (l_com == NULL)
return(NULL);
else
{
pmax = l_com; vmax = (pmax -> nombre) * (pmax -> prix);
for (pcour = l_com -> suiv; pcour != NULL; pcour = pcour ->suiv)
{
vcour = (pcour -> nombre * pcour -> prix);
if (vcour > vmax)
{
vmax = vcour;
pmax = pcour;
}
}
return(pmax);
}
165
}
166
/*********************************************************************/
/* main */
/*********************************************************************/
int main(void)
{
FILE * fi;
struct commande *l_com = NULL; /* liste des commandes */
struct commande *prec,*cour; /* pour la commande précédente
et courante */
int val_ret; /* valeur de retour de fscanf */
167
printf("La liste de commandes est vide\n");
else
{
for (cour = l_com; cour != NULL; cour = cour -> suiv)
print_com(*cour);
168
6.13 Les champs de bits
6.13.1 Généralités
Il est parfois nécessaire pour un programmeur de décrire en termes de bits la
structure d’une ressource matérielle de la machine. Un exemple typique est la
programmation système qui nécessite de manipuler des registres particuliers
de la machine. Par exemple, dans le manuel du mc 68030 de Motorola, le
registre d’état est ainsi décrit :
– bit 0 : carry ;
– bit 1 : overflow ;
– bit 2 : zéro ;
– bit 3 : négatif ;
– bit 4 : extension ;
– bit 11 : inutilisé ;
struct sr
{
unsigned int trace : 2;
169
unsigned int priv : 2;
unsigned int : 1; /* inutilisé */
unsigned int masque : 3;
unsigned int : 3; /* inutilisé */
unsigned int extend : 1;
unsigned int negative : 1;
unsigned int zero : 1;
unsigned int overflow : 1;
unsigned int carry : 1;
};
On voit que le langage C accepte que l’on ne donne pas de nom aux champs
de bits qui ne sont pas utilisés.
6.13.2 Contraintes
1. Les seuls types acceptés pour les champs de bits sont int, unsigned
int et signed int.
2. L’ordre dans lequel sont mis les champs de bits à l’intérieur d’un mot
dépend de l’implémentation, mais généralement, dans une machine little
endian les premiers champs décrivent les bits de poids faibles et les
derniers champs les bits de poids forts, alors que c’est généralement
l’inverse dans une machine big endian.
4. Un champ de bits n’a pas d’adresse, on ne peut donc pas lui appliquer
l’opérateur adresse de (&).
170
6.14 Les énumérations
Nous avons vu au paragraphe 1.10.2 que l’on pouvait déclarer des constantes
nommées de la manière suivante :
qui déclare les identificateurs LUNDI, MARDI, etc. comme étant des constantes
entières de valeur 0, 1, etc. Ce qui n’avait pas été dit à ce moment là, c’est que
les énumérations fonctionnent syntaxiquement comme les structures : après le
mot-clé enum il peut y avoir un identificateur appelé étiquette d’énumération
qui permettra plus loin dans le programme de déclarer des variables de type
énumération. Exemple :
L’exemple ci-dessus est conforme à ce qui nous semble être de bonnes rè-
gles de programmation : déclarer d’abord le type énumération en lui donnant
un nom grâce à une étiquette d’énumération et ensuite utiliser ce nom pour
déclarer des variables. Cependant, comme pour les structures, le langage C
permet de déclarer des variables dans la déclaration du type énumération,
éventuellement en omettant l’étiquette d’énumération. Exemples :
171
6.15 Les unions
Il est parfois nécessaire de manipuler des variables auxquelles on désire affecter
des valeurs de type différents. Supposons que l’on désire écrire un package
mathématique qui manipulera des nombres qui seront implémentés par des
int, tant que la précision des entiers de la machine sera suffisante et qui
passera automatiquement à une représentation sous forme de flottants dès
que ce ne sera plus le cas. Il sera nécessaire de disposer de variables pouvant
prendre soit des valeurs entières, soit des valeurs flottantes.
Ceci peut se réaliser en C, grâce au mécanisme des unions. Une définition
d’union a la même syntaxe qu’une définition de structure, le mot clé struct
étant simplement remplacé par le mot clé union. Exemple :
union nombre
{
int i;
float f;
}
union nombre n;
cette variable pourra posséder soit une valeur entière, soit une valeur flottante,
mais pas les deux à la fois.
172
n.i = 10;
si on désire lui faire posséder une valeur flottante, on écrira :
n.f = 3.14159;
struct arith
{
enum type typ_val; /* indique ce qui est dans u */
union
{
int i;
float f;
} u;
};
la struct arith a deux membres typ_val de type int, et u de type union
d’int et de float. On déclarera des variables par :
struct arith a1,a2;
puis on pourra les utiliser de la manière suivante :
a1.typ_val = ENTIER;
a1.u.i = 10;
a2.typ_val = FLOTTANT;
a2.u.f = 3.14159;
173
Si on passe en paramètre à une procédure un pointeur vers une struct arith,
la procédure testera la valeur du membre typ_val pour savoir si l’union reçue
possède un entier ou un flottant.
#define I u.i
#define F u.f
a1.typ_val = ENTIER;
a1.I = 10;
174
Chapter 7
Les expressions
Ce chapitre débute par l’étude des conversions, problème qui avait été à
peine effleuré quand nous avions parlé des opérateurs. Il se poursuit par la
présentation des opérateurs non encore vus et se termine par l’étude de la
sémantique des expressions.
175
les flottants. Cette situation se retrouve dans les langages de programmation
de bas niveau (les assembleurs) où le programmeur doit utiliser des opérateurs
différents pour réaliser la même opération (au sens mathématique du terme)
selon qu’elle porte sur des entiers ou des flottants. Les langages de program-
mation de haut niveau par contre, surchargent les symboles des opérateurs
arithmétiques de manière à ce que le même symbole puisse réaliser une opéra-
tion indifféremment entre entiers ou entre flottants : le symbole + permet de
réaliser l’addition de deux entiers ou deux flottants. Ceci est déjà une facil-
ité agréable, mais il est possible d’aller plus loin. Le langage peut autoriser
le programmeur à donner aux opérateurs des opérandes de types différents,
charge au compilateur de faire une conversion de type sur l’un ou l’autre des
opérandes pour les amener à un type commun.
Enfin, il se peut que le langage offre au programmeur la possibilité de
demander explicitement une conversion de type : si le langage pascal n’offre
pas une telle possibilité, le langage C par contre dispose d’un opérateur de
conversion de type.
176
7.1.3 L’ensemble des conversions possibles
Conversions vers un type entier
Seuls les types entiers et flottants peuvent être convertis en un type flottant.
Là aussi, la règle est de préserver la valeur si possible, sinon c’est un cas
d’overflow ou d’underflow.
177
Conversion vers un type pointeur
Les différentes possibilités sont les suivantes :
– Un type pointeur vers T 1 peut être converti en un type pointeur vers
T 2 quels que soient T 1 et T 2.
– Une valeur entière non nulle peut être convertie en un type pointeur
vers T quel que soit T , mais cela est explicitement non portable.
Nous avons vu précédemment au paragraphe 4.1 :
– Toute expression de type tableau de x est convertie en type pointeur
vers x.
Il y a une règle similaire concernant les fonctions :
– Toute expression de type fonction retournant x est convertie en type
pointeur vers fonction retournant x.
178
– valeur rendue par une fonction : l’opérande de return n’a pas le
type indiqué dans la déclaration de la fonction.
Domaine d’application
La promotion des entiers est appliquée à l’opérande des opérateurs unaires +,
- et ~, ainsi qu’aux deux opérandes des opérateurs de décalage >> et <<. La
promotion des entiers est également utilisée dans la définition des conversions
arithmétiques habituelles.
La règle
Une valeur de type char, un short int ou un champ de bits, ou d’une version
signée ou non signée des précédents, peut être utilisée dans un contexte où un
int ou un unsigned int est demandé. Cette valeur est convertie en un int
ou un unsigned int d’une manière (hélas) dépendante de l’implémentation :
179
7.1.6 Les conversions arithmétiques habituelles
Domaine d’application
Les conversions arithmétiques habituelles sont réalisés sur les opérandes de
tous les opérateurs arithmétiques binaires sauf les opérateurs de décalage >>
et << ainsi que sur les second et troisième opérandes de l’opérateur ?:.
La règle
1. Si un opérande est de type long double, l’autre opérande est converti
en long double.
4. Sinon la promotion des entiers est réalisée sur les deux opérandes. En-
suite :
180
Discussion
Les points 1, 2, 3 sont faciles à comprendre : si les deux opérandes sont
flottants, celui de moindre précision est converti dans le type de l’autre. Si
un seul des opérandes est de type flottant, l’autre est converti dans ce type.
On aborde le point 4 si les deux opérandes sont des variétés d’entiers
courts, normaux ou longs, signés ou non signés. On applique alors la promo-
tion des entiers, de manière à se débarrasser des entiers courts. À la suite de
cela, il n’y plus comme types possibles que int, unsigned int, long int et
unsigned long int.
Si l’on excepte les cas où les deux types sont identiques, le reste des règles
peut se résumer dans le tableau suivant :
if (i < -1 )
printf("Bizarre, bizarre ...\n");
else printf ("Tout semble normal\n");
}
imprimera le message Bizarre, bizarre ..., pouvant laisser croire que
pour le langage C, 0 est inférieur à −1.
181
L’explication est la suivante : l’opérateur < a un opérande de type
unsigned int (la variable i), et un autre opérande de type int (la con-
stante -1). D’après le tableau des conversions donné ci-dessus, on voit que
dans un tel cas, les opérandes sont convertis en unsigned int. Le compila-
teur génère donc une comparaison non signée entre 0 et 4294967295 (puisque
-1 = 0xffffffff = 4294967295), d’où le résultat.
Pour que tout rentre dans l’ordre, il suffit d’utiliser l’opérateur de conver-
sion pour prévenir le compilateur de ce qu’on veut faire :
int main(void)
{
unsigned int i = 0;
Là où tout se complique c’est qu’on peut utiliser des entiers non signés
sans le savoir ! Considérons le programme suivant :
182
int main(void)
{
if (sizeof(int) < -1)
printf("Bizarre, bizarre ...\n");
else printf ("Tout semble normal\n");
}
le lecteur a sans doute deviné qu’il va imprimer le message Bizarre, bizarre
..., et cependant les entiers n’ont pas une longueur négative ! L’explication
est la suivante : l’opérateur sizeof rend une valeur dont le type est non signé.
Voici ce que dit exactement la norme : « La valeur du résultat [ de sizeof ]
dépend de l’implémentation, et son type (un type entier non signé) est size_t
qui est définit dans le fichier d’include stddef.h ». Dans notre exemple, le
compilateur a généré une comparaison non signée entre 4 (sizeof(int)) et
4 294 967 295, d’où le résultat.
Recommandations
1. Ne jamais mélanger des entiers signés et non signés dans des compara-
isons : utiliser l’opérateur de conversion pour amener l’opérateur de
comparaison à avoir des opérandes de même type.
2. Bien noter que l’opérateur sizeof rend une valeur de type entier non
signé.
183
expression :
⇒ ~ expression
• Sémantique :
expression est évaluée et doit délivrer une valeur de type entier,
l’opération non bit à bit est réalisée sur cette valeur, et le résultat obtenu
est la valeur de l’expression ~.
• Sémantique :
Les deux expressions sont évaluées et doivent délivrer des valeurs de
type entier, le et bit à bit est réalisé, et la valeur obtenue est la valeur
de l’expression &.
• Sémantique :
Les deux expressions sont évaluées et doivent délivrer des valeurs de
type entier, le ou bit à bit est réalisé, et la valeur obtenue est la valeur
de l’expression |.
184
• Sémantique :
Les deux expressions sont évaluées et doivent délivrer des valeurs de
type entier, le ou exclusif bit à bit est réalisé, et la valeur obtenue est la
valeur de l’expression ^.
• Sémantique :
Les deux expressions sont évaluées et doivent délivrer des valeurs de
type entier, la valeur de expression 1 est décalée à gauche de expression2
bits en remplissant les bits libres avec des zéros. Le résultat obtenu est
la valeur de l’expression <<.
• Sémantique :
Les deux expressions sont évaluées et doivent délivrer des valeurs de
type entier, la valeur de expression 1 est décalée à droite de expression2
bits. Si expression 1 délivre une valeur unsigned, le décalage est un
décalage logique : les bits libérés sont remplis avec des zéros. Sinon, le
décalage peut être logique ou arithmétique (les bits libérés sont remplis
avec le bit de signe), cela dépend de l’implémentation.
185
7.2.7 Opérateur conditionnel
• Syntaxe :
expression :
⇒ expression 1 ? expression 2 : expression 3
• Sémantique :
expression 1 est évaluée et doit délivrer une valeur de type entier. Si
cette valeur est :
Exemples
Cet opérateur permet de remplacer une instruction if :
max = a > b ? a : b;
On peut utiliser cet opérateur en cascade, mais la lisibilité en souffre :
printf("i est %s",
i < 0 ? "negatif\n"
: i > 0 ? "positif\n"
: "nul\n");
• Sémantique :
expression 1 est évaluée et sa valeur ignorée. expression 2 est évaluée et
sa valeur est la valeur de l’expression expression 1 , expression 2 .
186
Remarque
Étant donné que la valeur de expression 1 est ignorée, pour qu’une telle con-
struction ait un sens, il faut que expression 1 fasse un effet de bord. On peut
écrire par exemple :
i = (j = 2 , 1);
ce qui est une manière particulièrement horrible d’écrire :
i = 1;
j = 2;
Une utilisation agréable par contre de l’opérateur virgule est dans les
expressions d’une boucle for. Si on désire écrire une boucle for qui utilise
deux index, il est utile d’écrire par exemple :
• Syntaxe :
expression :
⇒ lvalue += expression
• Sémantique :
lvalue = lvalue + expression
187
7.2.10 Opérateur conversion
• Syntaxe :
expression :
⇒ ( type ) expression
• Sémantique : expression est évaluée et convertie dans le type indiqué
par type.
Note
Dans le jargon C, l’opérateur de conversion de type s’appelle un cast. Dans
le vocabulaire des langages de programmation en général, une conversion de
type s’appelle en anglais une coertion, que l’on peut traduire par contrainte
ou coercition. Le mot anglais cast signifie plâtre (pour maintenir un membre
brisé), il donne donc bien une idée de contrainte, mais c’est quand même un
choix bizarre.
Exemples d’utilisation
L’opérateur de conversion est devenu moins utile avec la normalisation du
langage C. Dans k&r C, il était utilisé essentiellement pour deux raisons :
1. à cause de l’absence de pointeur générique void *. En effet, les procé-
dures d’allocation de mémoire comme malloc étaient définies par :
188
Il reste cependant un certain nombre de situations où l’opérateur de con-
version est nécessaire. En voici un exemple. Il s’agit d’un programme qui
a pour but de déterminer si l’architecture de la machine est de type little
endian ou big endian. Il faut regarder l’ordre des octets dans un entier, d’où
la nécessité de l’opérateur de conversion. Ce programme suppose que les int
sont implémentés sur 4 octets.
189
int i = 0x01020304;
char *p;
sont des opérateurs à part entière. Cela signifie que ces opérateurs, que l’on
peut appeler opérateurs d’adressage, ont une priorité et sont en concurrence
avec les autres opérateurs pour déterminer la sémantique d’une expression.
Par exemple, la sémantique de l’expression *p++ ne peut se déterminer que
si l’on connaît les priorités relatives des opérateurs * et ++.
190
7.3.2 Priorité et associativité des opérateurs
Pour déterminer la sémantique d’une expression il faut non seulement con-
naître la priorité des opérateurs mais également leur associativité. En effet,
seule la connaissance de l’associativité de l’opérateur == permet de savoir si
a == b == c signifie (a == b) == c ou si elle signifie a == (b == c).
Un opérateur a une associativité à droite quand :
a op b op c signifie a op ( b op c).
Un opérateur a une associativité à gauche quand :
a op b op c signifie (a op b) op c.
Nous donnons ci-dessous le tableau exhaustif des opérateurs avec leurs
priorités et leurs associativité.
priorité Opérateur Associativité
16 () [] -> . ++ 1 -- 2 G
15 ! ~ ++ 3 -- 4 - 5 + 6 * 7 & 8 sizeof D
14 conversion D
13 *9 / % G
12 + - G
11 << >> G
10 < <= > >= G
9 == != G
8 & 10 G
7 ^ G
6 | G
5 && G
4 || G
3 ?: D
2 = += -= *= /= %= >>= <<= &= ^= |= D
1 , G
Discussion
Les choix faits pour les priorités des opérateurs sont assez mauvais, les con-
cepteurs du langage eux-mêmes en conviennent. 11 Les choix les plus irritants
11
The C programming langage, page 3 : C, like any other language, has its blemishes.
Some of the operators have the wrong precedence;
191
sont les suivants :
– La précédence des opérateurs bits à bits est plus petite que celle des
opérateurs de comparaison. Donc a&b == c ne signifie pas (a&b) ==
c, mais a & (b==c).
– La précédence des opérateurs de décalage est plus petite que celle des
opérateurs de + et -. Donc a << 4 + b signifie a << (4 + b).
Recommandation
Il est considéré comme un bon style de programmation en C, de systématique-
ment parenthéser les expressions dès qu’elles comportent d’autres opérateurs
que les opérateurs de l’arithmétique usuelle.
t[i] = f();
7.4 Récréation
Voici en illustration de l’opérateur ~, la contribution de Jack Applin a la
compétition du code C le plus obscur (ioccc) de 1986. Ce programme a la
propriété extraordinaire d’être un source valide à la fois pour le shell /bin/sh,
le langage C et fortran ! Voici le source :
192
* This program works under cc, f77, and /bin/sh.
*
*/; main() {
write(
cat-~-cat
/*,’(
*/
,"Hello, world!"
,
cat); putchar(~-~-~-cat); } /*
,)’)
end
*/
La version shell
La commande cat =13 est une commande incorrecte (à cause du blanc entre
cat et le signe =), mais comme l’erreur standard est redirigée sur /dev/null,
le message d’erreur n’apparaît pas. Ensuite, l’echo imprime « Hello world »,
puis le shell fait exit. Le reste du source lui est donc indifférent. Ensuite,
les deux versions C et fortran sont mélangées grâce à un emploi judicieux
des commentaires (en fortran, toute ligne commençant par * ou c est un
commentaire).
La version fortran
Une fois débarrassé des commentaires fortran, le source devient :
193
La version C
Après nettoyage des commentaires C, le source original devient :
cat =13 ;
main()
{
write( cat-~-cat ,"Hello, world!" , cat);
putchar(~-~-~-cat);
}
194
Chapter 8
Le préprocesseur
195
1. un nom bien choisi permet d’expliciter la sémantique de la constante.
Exemple : #define NB_COLONNES 100.
#define IF if(
#define THEN ){
#define ELSE } else {
#define ELIF } else if (
#define FI ;}
#define BEGIN {
#define END }
#define SWITCH switch(
#define IN ){
#define ENDSW }
#define FOR for(
#define WHILE while(
#define DO ){
#define OD ;}
#define REP do{
#define PER }while(
196
#undef DONE
#define DONE );
#define LOOP for(;;){
#define POOL }
assign(n,v)
NAMPTR n;
STRING v;
{
IF n->namflg&N_RDONLY
THEN failed(n->namid,wtfailed);
ELSE replace(&n->namval,v);
FI
}
cc -c -DNB_LIGNES=24 fic.c
197
nom valeur de la macro forme
syntaxique
__LINE__ numéro de la ligne courante du programme source entier
__FILE__ nom du fichier source en cours de compilation chaîne
__DATE__ la date de la compilation chaîne
__TIME__ l’heure de la compilation chaîne
__STDC__ 1 si le compilateur est iso, 0 sinon entier
f()
{
int i,j,k;
198
i = min(j,k); /* équivalent à : i = j < k ? j : k ;
i = max(j,k); /* équivalent à : i = j < k ? k : j ;
}
Attention
La distinction entre macro avec et sans paramètre se fait sur le caractère
qui suit immédiatement le nom de la macro : si ce caractère est une paren-
thèse ouvrante c’est une macro avec paramètres, sinon c’est une macro sans
paramètre. En particulier, si après le nom de la macro il y a un blanc avant
la parenthèse ouvrante, ça sera une macro sans paramètre. Exemple :
Exemple
Cet exemple est tiré du source de Linux. Il s’agit d’un fragment de gestion
de la mémoire virtuelle, une structure page a été définie :
struct page {
struct inode *inode;
unsigned long offset;
struct page *next_hash;
atomic_t count;
unsigned flags; /* atomic flags,
some possibly updated asynchronously */
... /* d’autres champs */
};
Dans cette structure, le champs flags est un ensemble de bits définis ci-après :
199
#define PG_error 1
#define PG_referenced 2
#define PG_uptodate 3
#define PG_free_after 4
#define PG_decr_after 5
Puis le programmeur a défini des macros pour tester commodément ces bits
à l’aide de la fonction test_bit définie par ailleurs :
/* Make it prettier to test the above... */
#define PageLocked(page) \
(test_bit(PG_locked, &(page)->flags))
#define PageError(page) \
(test_bit(PG_error, &(page)->flags))
#define PageReferenced(page) \
(test_bit(PG_referenced, &(page)->flags))
#define PageUptodate(page) \
(test_bit(PG_uptodate, &(page)->flags))
#define PageFreeAfter(page) \
(test_bit(PG_free_after, &(page)->flags))
#define PageDecrAfter(page) \
(test_bit(PG_decr_after, &(page)->flags))
f()
{
int i, t[10];
200
Le second paramètre passé à la macro (i]) ne correspond syntaxiquement à
rien, mais le résultat de l’expansion de la macro est correct.
#define CARRE(a) a * a
une occurrence de CARRE(a+b) aura comme expansion a+b * a+b ce qui est
différent du (a+b) * (a+b) qui était désiré. De la même manière !CARRE(x)
aura comme expansion !x * x ce qui est différent du !(x * x) qui était
désiré.
On recommande donc de toujours respecter deux règles dans la définition
d’une macro devant être utilisée dans des expressions :
201
8.1.5 Macros générant des instructions
Tous les exemples donnés jusqu’ici sont des exemples de macros générant des
expressions. Les macros peuvent aussi générer des instructions et là aussi il
y a des pièges à éviter. Supposons qu’ayant à écrire un grand nombre de fois
un appel de fonction avec test d’erreur, on définisse la macro suivante :
#define F(x) if (!f(x)) { printf("erreur\n"); exit(1); }
La macro pourra s’appeler comme une fonction (avec un ; à la fin) dans un
contexte de liste d’instructions :
{
...
F(i);
...
}
Par contre, dans un contexte d’instruction (et non de liste d’instructions), il
ne faudra pas mettre de ; à la fin :
do F(a) while ( ... );
alors qu’il le faudrait si il s’agissait d’une fonction :
do f(a); while ( ... );
Mais le pire reste à venir : voyons ce qui se passe si on utilise la macro F dans
un if avec else :
if ( ... )
F(i)
else
...
Il suffit d’imaginer l’expansion de la macro :
if ( ... )
if (!f(x)) { printf("erreur\n"); exit(1); }
else
...
pour comprendre le problème : le else va être raccroché au if de la macro,
ce qui n’est pas ce qu’a voulu le programmeur.
202
Recommandation
Pour toute macro générant des instructions, on recommande d’englober les
instructions générées dans la partie instruction d’un do ... while (0).
Notre exemple s’écrit ainsi :
#define F(x) do { \
if (!f(x)) { printf("erreur\n"); exit(1); } \
} while (0)
et tous les problèmes précédents s’évanouissent.
203
ensemble-de-lignes 1
#else
ensemble-de-lignes 2
#endif
le préprocesseur évalue expression. Si expression délivre une valeur non
nulle, ensemble-de-lignes 1 est compilé et ensemble-de-lignes 2 est ignoré, sinon
ensemble-de-lignes 1 est ignoré et ensemble-de-lignes 2 est compilé.
204
8.2.3 L’opérateur defined
L’opérateur defined est un opérateur spécial : il ne peut être utilisé que dans
le contexte d’une commande #if ou #elif. Il peut être utilisé sous l’une des
deux formes suivantes : defined nom ou bien : defined ( nom ). Il délivre
la valeur 1 si nom est une macro définie, et la valeur 0 sinon. L’intérêt de
cet opérateur est de permettre d’écrire des tests portant sur la définition de
plusieurs macros, alors que #ifdef ne peut en tester qu’une.
#if defined(SOLARIS) || defined(SYSV)
8.2.5 Usage
La compilation conditionnelle a pour but essentiel d’adapter le programme à
son environnement d’exécution : soit il s’agit d’un programme système de-
vant s’adapter au matériel sur lequel il s’exécute, soit il s’agit d’un programme
d’application qui doit s’adapter au système sur lequel il s’exécute. Prenons
par exemple le système unix qui existe en deux grandes variantes : la vari-
ante bsd et la variante system v. La routine de recherche d’un caractère
déterminé dans une chaîne s’appelle index en bsd et strchr en system v,
mais l’interface est le même. Voici comment on peut écrire un programme
se compilant et s’exécutant sur les deux plate-formes : le programmeur peut
décider d’utiliser un nom à lui, par exemple RechercheCar, et de le définir
comme ci-dessous.
205
#if defined(HAS_INDEX)
#define RechercheCar index
#elif defined(HAS_STRCHR)
#define RechercheCar strchr
#else
#error "Impossible de réaliser RechercheCar"
#endif
cc -c -DHAS_INDEX fichier.c
ou par :
cc -c -DHAS_STRCHR fichier.c
8.3 Récréation
Quel est le plus petit programme possible en C ?
Mark Biggar a été un vainqueur de la compétition du code C le plus obscur
(ioccc) avec un programme ne comportant qu’une seule lettre : P ! Pour
arriver à ce résultat, il avait compliqué un petit peu la ligne de commande de
compilation :
206
main()
{
char c;
while(read(0,&c,1) >0)
if (c!=015) c=write(1,&c,1);
}
207
208
Chapter 9
Les déclarations
Nous n’avons vu jusqu’à présent que des exemples de déclarations, il est temps
maintenant de voir les déclarations de manière plus formelle.
– les déclarations qui font référence à un objet défini ailleurs, ce sont les
déclarations de référence.
209
– un nom de fonction défini dans la même unité de compilation, pour
résoudre le cas d’appel récursif : la fonction f1 appelle f2 qui appelle
f3, ... qui appelle fn qui appelle f1.
Exemples :
int i; /* définition de i */
extern int j; /* référence à un entier défini ailleurs */
int t1[20]; /* définition de t */
extern t2[]; /* référence à un tableau t2 défini ailleurs */
210
/* min possède la partie instruction : c’est une définition */
int min(int a, int b)
{
return(a < b ? a : b);
}
211
– un identificateur déclaré dans une instruction composée a une portée qui
s’étend du point de déclaration jusqu’à la fin de l’instruction composée ;
Exemple :
... /* instructions 1 */
k:
if (...)
{
int l; /* déclaration à l’intérieur d’une instruction composée
... /* instructions 2
}
} /* fin de proc1
212
9.3 Visibilité des identificateurs
Dans le langage C, l’imbrication des instructions composées forme une struc-
ture classique de blocs, c’est à dire que les déclarations d’une instruction
composée englobée cachent les déclarations des instructions composées en-
globantes ayant le même nom. De surcroît, les déclarations d’une instruction
composée cachent les déclarations de même nom, qui sont à l’extérieur de
toute fonction. Exemple :
213
int i;
int j;
void proc1()
{
int i; /* cache le i précédent */
int k;
if (a > b)
{
int i; /* cache le i précédent */
int j; /* cache le j précédent */
int k; /* cache le k précédent */
...
}
}
struct st1
{
int i;
int j;
};
struct st2
{
int i;
double d;
214
};
int main(void)
{
struct st1 s1; /* déclaration de la variable s1 */
struct st2 s2; /* déclaration de la variable s2 */
s1.i = s2.i;
}
215
}i1,i2;
struct ii
{
int i; /* i est un champ de la struct ii */
int j;
}ii1,ii2;
int main(void)
{
i: /* i est une étiquette de branchement */
i = 1;
i1.i = 2;
ii1.i = 3;
goto i;
}
Remarque
Certains auteurs considèrent également qu’il existe un espace de noms pour
les noms définis à l’aide de la commande #define du macro-processeur. Nous
avons refusé cette vision des choses dans la mesure où pendant la phase de
compilation proprement dite, ces noms n’ont plus d’existence.
216
Dans les langages de programmation, il y a généralement un lien étroit
entre la portée de la déclaration d’une variable et sa durée de vie. Il est
classique en effet, qu’une variable globale (c’est à dire dont la déclaration se
trouve à l’extérieur de toute fonction), soit une variable statique, et qu’une
variable locale à une procédure ou fonction, soit une variable automatique.
Discussion
Cette liberté de donner à une variable locale une durée de vie égale à celle
du programme, permet de résoudre des problèmes du type exposé ci-dessous.
Imaginons une procédure qui pour une raison quelconque doit connaître com-
bien de fois elle a été appelée. Le programmeur a besoin d’une variable dont
la durée de vie est supérieure à celle de la procédure concernée, ce sera donc
une variable statique. Cependant cette variable doit être une variable privée
de la procédure, (il n’y a aucune raison qu’une autre procédure puisse en
modifier la valeur), il faut donc que ce soit une variable locale à la procédure.
En C, on programmera de la manière suivante :
void proc()
{
static int nb_appel = 0;
nb_appel++;
...
}
217
9.6 Classes de mémoire
9.6.1 Position du problème
Lors de l’exécution d’un programme C il y a trois zones de mémoire dif-
férentes, correspondant aux trois durées de vies possibles :
register int i;
218
9.6.2 Les spécificateurs de classe de mémoire
Il existe 5 mots-clés du langage que la grammaire nomme spécificateur de
classe de mémoire. Il s’agit des mots-clés suivants :
auto Ce spécificateur de classe mémoire n’est autorisé que pour les variables
locales à une instruction composée. Il indique que la variable concernée
a une durée de vie locale à l’instruction composée. Si la déclaration
d’une variable locale ne comporte pas de spécificateurs de classe de
mémoire, c’est auto qui est pris par défaut (en pratique, on n’utilise
donc jamais auto vu qu’il est implicite1 ).
Exemple :
{
auto int i;
...
}
219
void g() /* g sera exportée par l’éditeur de liens */
{
...
}
typedef Ce spécificateur n’a rien à voir avec les classes de mémoire : il sert
à définir des types. Son utilité sera vue plus loin.
Discussion
On peut faire les critiques suivantes :
1. auto ne peut servir que pour les variables locales, mais si on ne le met
pas, il est pris par défaut. Conclusion : il ne sert à rien.
220
3. register a été introduit dans le langage pour optimiser les pro-
grammes. Avec les techniques modernes de compilation, il ne sert à
rien car le compilateur est mieux à même que le programmeur d’allouer
les registres de la machine de manière efficace. Ne pas oublier en outre,
qu’il y a une contrainte attachée à l’utilisation de variables register :
l’impossibilité de leur appliquer l’opérateur &.
4. extern et typedef n’ont rien à voir avec les classes de mémoire : ils ne
sont là que pour des raisons syntaxiques.
Méthode du common
Dans cette méthode, les variables partagées sont déclarées comme appar-
tenant à un segment spécial, appelé common. Toute variable du common
221
est référençable par n’importe quelle unité de compilation. C’est la méthode
utilisée par fortran.
Exemples :
Les contraintes
Au moment de l’édition de liens, pour un identificateur donné, n unités de
compilation l’auront défini, p l’auront référencé, et q auront demandé une
mise dans le common. Les contraintes sont les suivantes :
– si n vaut 0, alors q ne peut être nul : il ne peut pas y avoir que des
références.
222
Voyons les choses sous l’angle de ce qui est autorisé. On peut avoir :
En pratique
Les bons auteurs recommandent de s’en tenir à la stricte méthode des réfs
et défs, mais la lecture de nombreux sources montre que c’est la méthode
du common qui a la faveur des programmeurs. Cette méthode à en effet un
avantage pratique : dans toutes les unités de compilations, la déclaration d’un
nom est strictement la même. Cette déclaration pourra donc être mise dans
un fichier qui sera inclus (par #include) dans toutes les unités de compilation
qui utilisent ce nom.
La méthode des réfs et des défs par contre, impose d’avoir au moins deux
déclarations différentes pour un même nom : une pour la définition et une
autre (qui peut être dans un fichier d’inclusion) pour la référence.
223
des défs, avec une distinction facile à faire : comme on l’a vu en 9.1.2, si une
déclaration de fonction comporte une partie instruction c’est une définition,
sinon c’est une référence.
typedef struct {
char nom[20];
int no_ss;
} personne;
224
9.9.1 Restriction d’un type de base
Il est parfois nécessaire de manipuler des variables qui ne peuvent prendre
comme valeurs qu’un sous-ensemble des valeurs d’un type de base. Supposons
que nous voulions manipuler des booléens. Comme le type booléen n’existe
pas en C90, il faudra utiliser des int, en se restreignant à deux valeurs, par
exemple 0 et 1. Il est alors intéressant de redéfinir à l’aide d’un typedef, le
type int. On écrira par exemple :
#define VRAI 1
#define FAUX 0
typedef int BOOLEAN;
BOOLEAN b1,b2;
et les utiliser :
b1 = VRAI;
if (b2 == FAUX) ...
mais bien entendu, ce sera à la charge du programmeur d’assurer que les vari-
ables b1 et b2 ne prennent comme valeurs que VRAI ou FAUX. Le compilateur
ne protestera pas si on écrit :
b1 = 10;
225
struct personne
{
...
}
les déclarations de variables se feront par :
struct personne p1,p2;
alors que si on déclare :
typedef struct
{
...
} PERSONNE;
les déclarations de variables se feront par :
PERSONNE p1,p2;
on voit que la seconde méthode permet d’éviter d’avoir à répéter struct.
De la même manière, en ce qui concerne les pointeurs, il est plus difficile
d’écrire et de comprendre :
struct personne
{
...
};
struct personne *p1,*p2; /* p1 et p2 pointeurs vers des struct
que la version suivante qui donne un nom parlant au type pointeur vers struct :
typedef struct
{
...
} PERSONNE;
226
9.9.3 Définition de types opaques
Dans le cadre de l’écriture de programme en plusieurs unités de compila-
tion, il est souvent utile de définir un type de manière opaque, c’est à dire
d’en laisser libre l’utilisation sans que l’utilisateur n’ait à connaître sa défini-
tion. C’est exactement ce que réalise la bibliothèque standard pour le type
FILE : le programmeur sait que fopen() rend une valeur de type FILE * et
que cette valeur doit être passée en paramètre des fonctions d’entrées-sorties
fprintf(), fputc(), fputs() etc. Il y a beaucoup d’autres exemples de ce
type dans la bibliothèque standard.
Qualificatif const
Une variable dont le type est qualifié par const ne peut pas être modifiée.
Le programmeur entend ainsi se protéger contre une erreur de programma-
tion. Ceci n’est utile que pour les paramètres d’une fonction, lorsqu’on désire
protéger le paramètres effectifs de la fonction en les mettant en « lecture
seulement » pour la fonction.
Qualificatif volatile
En qualifiant par volatile le type d’une variable, le programmeur prévient le
compilateur que cette variable peut être modifiée par un moyen extérieur au
programme. Ceci se produit lorsqu’on interagit avec des parties matérielles
de la machine : coupleurs d’entrées-sorties généralement. Lorsqu’une variable
est de type volatile le compilateur ne doit pas procéder aux optimisations
qu’il réalise sur les variables normales.
Les qualificatifs de type deviennent pénibles en conjonction avec les poin-
teurs car on a les trois possibilités :
227
– le pointeur et l’objet pointé sont qualifiés.
type sémantique
const char c; caractère constant
const char *p; pointeur vers caractère constant
char * const p; pointeur constant vers caractère
const char * const p; pointeur constant vers caractère constant
va_end doit être appelée après toutes les utilisations de va_arg. La macro
va_end admet un seul paramètre : la variable ap.
228
Rien n’est prévu pour communiquer à la fonction le nombre et le type des
paramètres effectivement passés : c’est un problème à la charge du program-
meur.
229
9.11.1 Exemple 1
Ci-dessous l’exemple de la fonction addn qui réalise la somme de ses
paramètres optionnels.
#include <stdio.h>
#include <stdarg.h>
/********************************************************************/
/* */
/* addn */
/* */
/* But: */
/* réalise l’addition d’un nombre variable de paramètres */
/* */
/********************************************************************/
int addn(int nbopd, ...) /* nbopd = nombre d’opérandes du add */
{
int i, s = 0;
va_list(ap); /* déclaration de ap */
va_start(ap,nbopd); /* initialisation de ap */
for( i = 1; i <= nbopd; i++)
s = s + va_arg(ap,int); /* va_arg() donne le paramètre courant */
va_end(ap); /* on a fini */
return(s);
}
/********************************************************************/
/* main */
/********************************************************************/
int main(void)
{
printf("resu = %d\n",addn(3,10,11,12)); /* imprime 33 */
return 0;
}
230
va_arg(ap,int).
9.11.2 Exemple 2
Dans la bibliothèque standard, il y a deux fonctions utilisées couramment
qui admettent un nombre variable de paramètres : ce sont printf et scanf.
Voici leur déclaration dans stdio.h :
231
9.12 Syntaxe des déclarations
La grammaire des déclarations est la suivante :
déclaration :
⇒ spécificateurs-de-déclaration liste-de-déclarateurs-init option ;
spécificateurs-de-déclaration :
⇒ spécificateur-de-classe-mémoire spécificateurs-de-déclaration option
⇒ spécificateur-de-type spécificateurs-de-déclaration option
⇒ qualificatif-de-type spécificateurs-de-déclaration option
liste-de-déclarateurs-init :
⇒ déclarateur-init
⇒ liste-de-déclarateurs-init , déclarateur-init
déclarateur-init :
⇒ déclarateur
⇒ déclarateur = initialisateur
spécificateur-de-classe-mémoire :
⇒ auto
⇒ extern
⇒ static
⇒ register
⇒ typedef
spécificateur-de-type :
⇒ void
⇒ char
⇒ short
⇒ int
⇒ long
⇒ float
⇒ double
⇒ signed
232
⇒ unsigned
⇒ spécificateur-de-struct-ou-union
⇒ spécificateur-d-énumération
⇒ nom-de-typedef
spécificateur-de-struct-ou-union :
⇒ struct-ou-union identificateur option { liste-de-déclarations-de-
struct }
⇒ struct-ou-union identificateur
struct-ou-union :
⇒ struct
⇒ union
liste-de-déclarations-de-struct :
⇒ déclaration-de-struct
⇒ liste-de-déclarations-de-struct déclaration-de-struct
déclaration-de-struct :
⇒ liste-de-spécificateurs-et-qualificatifs liste-de-déclarateurs-de-
struct ;
liste-de-spécificateurs-et-qualificatifs :
⇒ spécificateur-de-type liste-de-spécificateurs-et-qualificatifs option
⇒ qualificatif-de-type liste-de-spécificateurs-et-qualificatifs option
liste-de-déclarateurs-de-struct :
⇒ déclarateur-de-struct
⇒ liste-de-déclarateurs-de-struct , déclarateur-de-struct
déclarateur-de-struct :
⇒ déclarateur
⇒ déclarateur option : expression-constante
spécificateur-d-énumération :
⇒ enum identificateur option { liste-d-énumérateurs }
233
⇒ enum identificateur
liste-d-énumérateurs :
⇒ énumérateur
⇒ liste-d-énumérateurs , énumérateur
énumérateur :
⇒ identificateur
⇒ identificateur = expression constante
qualificatif-de-type :
⇒ const
⇒ volatile
déclarateur :
⇒ pointeur option déclarateur-direct
déclarateur-direct :
⇒ identificateur
⇒ ( déclarateur )
⇒ déclarateur-direct [ expression-constante option ]
⇒ déclarateur-direct ( liste-de-types-de-paramètres )
⇒ déclarateur-direct ( liste-d-identificateurs option )
pointeur :
⇒ * liste-de-qualificatifs-de-types option
⇒ * liste-de-qualificatifs-de-types option pointeur
liste-de-qualificatifs-de-types :
⇒ qualificatif-de-type
⇒ liste-de-qualificatifs-de-types qualificatif-de-type
liste-de-types-de-paramètres :
⇒ liste-de-paramètres
⇒ liste-de-paramètres , ...
234
liste-de-paramètres :
⇒ déclaration-de-paramètre
⇒ liste-de-paramètres , déclaration-de-paramètre
déclaration-de-paramètre :
⇒ spécificateurs-de-déclaration déclarateur
⇒ spécificateurs-de-déclaration déclarateur-abstrait option
liste-d’identificateurs :
⇒ identificateur
⇒ liste-d’identificateurs , identificateur
initialisateur :
⇒ expression-d’affectation
⇒ { liste-d-initialisateurs }
⇒ { liste-d-initialisateurs , }
liste-d’initialisateurs :
⇒ initialisateur
⇒ liste-d’initialisateurs , initialisateur
Exemples
Dans la déclaration :
int i,j = 2;
int est un spécificateur-de-type et i,j = 2 est une liste-de-déclarateur-init
composée de deux déclarateur-init : i et j = 2. i est un déclarateur-init
sans la partie initialisateur, donc réduit à un déclarateur lui-même réduit à
un identificateur. j = 2 est un déclarateur-init comportant un initialisateur
(= 2) et un déclarateur réduit à un identificateur (j). Dans la déclaration :
int t[10];
t[10] est un déclarateur formé d’un déclarateur (t), suivi de [ suivi de
l’expression constante 10, suivi de ].
235
9.13 Sémantique des déclarations
La partie qui nécessite d’être explicitée est la partie de la grammaire concer-
nant les déclarateur. La sémantique est la suivante :
• il y a 3 constructeurs de type :
236
• les constructeurs de type peuvent se composer et sont affectés de prior-
ités. Les constructeurs ( ) et [ ] ont la même priorité, et celle-ci est
supérieure à la priorité du constructeur *.
c : char;
char c;
qu’il faut traduire par « de type char est c », ce qui est une inversion peu na-
turelle. Ensuite, l’identificateur qui est le nom de l’objet déclaré, au lieu d’être
mis en évidence dans la déclaration, est caché au beau milieu du déclarateur.
Exemple :
char **p[10];
237
D’autre part, le langage C fait partie des langages qui permettent au
programmeur, à partir de types de base et de constructeurs de type, de con-
struire des types complexes. Du point de vue du programmeur, il existe dans
le langage les constructeurs suivants :
char (*(*f())[])()
9.15 En pratique
Lorsqu’il s’agit d’écrire ou de comprendre un type compliqué, il est recom-
mandé non pas d’utiliser la grammaire des déclarateur, mais de partir d’une
utilisation du type, étant donné que la grammaire est faite de telle sorte que
les déclarations calquent très exactement les expressions.
2
f est une fonction retournant un pointeur vers un tableau de pointeurs vers une fonction
retournant un char
238
Voyons sur un exemple. Soit à déclarer un pointeur p vers une fonction re-
tournant un int. Considérons l’utilisation de p : à partir de p, il faut d’abord
utiliser l’opérateur indirection pour obtenir la fonction, soit *p. Ensuite, on
va appeler la fonction délivrée par l’expression *p, pour cela il faut écrire
(*p)()3 . Finalement cette expression nous délivre un int. La déclaration de
p s’écrira donc :
int (*p)();
De la même manière, pour interpréter un type compliqué, il vaut mieux
partir d’une utilisation. Soit à interpréter :
char (*f())[];
Imaginons une utilisation : (*f(i))[j]. L’opérateur appel de fonction étant
plus prioritaire que l’opérateur indirection, on applique d’abord l’appel de
fonction à f. Donc f est une fonction. Ensuite on applique l’opérateur indi-
rection, donc f est une fonction retournant un pointeur. On indexe le résultat,
donc f est une fonction retournant un pointeur vers un tableau. Finalement,
on voit que f est une fonction retournant un pointeur vers un tableau de char.
# la question :
3
A l’utilisation il faudra mettre les paramètres effectifs
4
Une version en ligne est disponible à l’url http://cdecl.org/
239
cdecl> declare p as pointer to function returning int
# la réponse
int (*p)()
cdecl>
240
Chapter 10
La bibliothèque standard
10.1 Diagnostic
241
fonction le paramètre est
isalnum une lettre ou un chiffre
isalpha une lettre
iscntrl un caractère de commande
isdigit un chiffre décimal
isgraph un caractère imprimable ou le blanc
islower une lettre minuscule
isprint un caractère imprimable (pas le blanc)
ispunct un caractère imprimable pas isalnum
isspace un caractère d’espace blanc
isupper une lettre majuscule
isxdigit un chiffre hexadécimal
242
10.4 Mathématiques <math.h>
10.4.1 Fonctions trigonométriques et hyperboliques
fonction sémantique
acos arc cosinus
asin arc sinus
atan arc tangente
atan2 arc tangente
cos cosinus
cosh cosinus hyperbolique
sin sinus hyperbolique
sinh sinus
tan tangente
tanh tangente hyperbolique
243
10.5 Branchements non locaux <setjmp.h>
L’instruction goto qui ne avons vu au paragraphe 3.15.2 ne permet de
réaliser des branchements qu’au sein d’une même procédure. Pour réaliser
des branchements à l’extérieur d’une procédure, il faut utiliser setjmp et
longjmp.
– signal permet d’installer une fonction qui sera exécutée sur réception
d’un signal ;
244
10.8.2 Accès aux fichiers
fonction description
fclose fermeture de fichier
fflush écriture sur fichier des buffers en mémoire
fopen ouverture de fichier
freopen ouverture de fichier
245
10.8.5 Entrées-sorties binaires
Pour lire et écrire des données binaires, on dispose de deux fonctions : fread
et fwrite.
fonction description
atof conversion de chaîne vers double
atoi conversion de chaîne vers int
atol conversion de chaîne vers long int
strtod conversion de chaîne vers double
strtol conversion de chaîne vers long int
strtoul conversion de chaîne vers unsigned long int
246
10.9.2 Génération de nombres pseudo-aléatoires
On dispose de rand et srand.
247
10.10 Manipulation de chaînes <string.h>
On dispose de fonctions pour :
– transformer : strxfrm ;
– initialiser : memset ;
248
Chapter 11
249
11.1.1 Les commentaires mono-lignes
Depuis C99, on peut utiliser des commentaires mono-lignes, avec la syntaxe
// commentaire. En C90, les seuls commentaires autorisés sont ceux util-
isant la syntaxe /* commentaire */, même si beaucoup de compilateurs ac-
ceptent les deux en pratique (GCC accepte les commentaires mono-lignes sauf
si on compile avec -ansi pour lui demander de se conformer plus strictement
à la norme).
{
int x;
x = 42;
int y; // déclaration en milieu de bloc
y = 42;
}
Une utilisation très pratique de cette fonctionnalité est de déclarer une vari-
able locale à une boucle for :
250
#include <stdbool.h>
#include <stdio.h>
int main() {
bool x = true;
if (x) {
printf("Oui\n");
}
}
251
11.3 Tableaux de taille variable
En C90, la taille d’un tableau doit être constante. Même une déclaration
aussi simple que la suivante est refusée par la norme (même si GCC l’accepte
par défaut) :
int main() {
int T[S];
// ...
}
void f(int m) {
int T[m]; // instanciation d’un tableau de taille m
// ...
int T2[T[42]];
}
11.4 Multithreading
La programmation multi-thread (ou programmation parallèle), qui était pos-
sible via des bibliothèques comme les pthread sur les systèmes Unix, intègre
la norme du langage en C11. La sémantique des accès mémoires sur des
programmes parallèles est définie, en ajoutant la notion de type atomique
(_Atomic et le fichier stdatomic.h). On peut déclarer des variables locales
à un thread (_Thread_local), et les primitives classiques comme les créa-
tion/destruction de thread, mutex et conditions sont définies dans le nouveau
fichier d’en-tête threads.h.
252
Appendix A
253
occidentale et qui est référencé iso-8859-1 ou iso-latin-1. Malheureusement,
les français ne sont pas de très bons lobbyistes dans les instances interna-
tionales, et aussi incroyable que cela puisse paraître, la ligature œ du français
a été oubliée !
• commandes de format
commande nom
carriage return CR
line feed LF
backspace BS
horizontal tabulation HT
vertical tabulation VT
space SP
form feed FF
254
sur la ligne suivante, sans aller en début de ligne. Pour obtenir l’effet
de « passage à la ligne » , il faut donc un caractère carriage return
suivi d’un caractère line feed (ou l’inverse). Dans le système unix, le
caractère choisi par convention comme signifiant « passage à la ligne »
est le caractère line feed, et c’est à la charge des pilotes de périphériques
de remplacer ce caractère logique par la suite de caractères nécessaires
pour obtenir un passage à la ligne suivante. Prenons le cas d’un pilote
de terminal écran clavier :
commande nom
shift out SO
shift in SI
escape ESC
255
séquence sémantique
escape [2A monter le curseur de 2 lignes
escape [4B descendre le curseur de 4 lignes
escape [3C décaler le curseur de 3 positions vers la droite
escape [1D décaler le curseur de 1 position vers la gauche
• commande de séparation
commande nom
file separator FS
group separator GS
record separator RS
unit separator US
end of medium EM
commande nom
start of header SOH
start of text STX
end of text ETX
end of transmission EOT
end of transmitted block ETB
enquiry ENQ
positive acknowledge ACK
negative acknowledge NAK
synchronisation SYN
data link escape DLE
null NUL
Les 10 premières commandes ont été crées pour construire des trames de
communication entre machines reliées par des lignes synchrones. Elles
256
sont complètement obsolètes de nos jours, où les communications se font
grâce à des réseaux dont les trames n’utilisent pas ces caractères.
La dernière commande null était utile à l’époque des téléimprimeurs
dont le temps de retour du chariot était plus grand que le temps
d’impression d’un caractère quelconque. Après avoir envoyé un car-
riage return, il fallait envoyer plusieurs null (en fonction de la vitesse
de la ligne) pour être sûr que le chariot était bien revenu en début de
ligne !
• commandes de périphérique
commande nom
device control 1 DC1
device control 2 DC2
device control 3 DC3
device control 4 DC4
Ces caractères ont été prévus pour donner des ordres spécifiques à cer-
tains périphériques. A l’époque des téléimprimeurs, ceux-ci possédaient
un lecteur-perforateur de ruban papier. Les codes device control étaient
utilisés pour commander ce lecteur-perforateur.
De nos jours device control 3 et device control 1 sont utilisés sous les
noms respectifs de xon et xoff pour réaliser du contrôle de flux. Les
caractères device control 3 et device control 1 sont affectés aux touches
Control-q et Control-s du clavier. Lorsqu’un pilote de terminal écran-
clavier gère le contrôle de flux, l’utilisateur peut taper Control-s pour
faire stopper une sortie trop rapide (pour se donner le temps de la lire
sur l’écran), et la faire continuer en tapant Control-q.
• commandes diverses
commande nom
cancel CAN
substitute SUB
delete DEL
bell BEL
257
Il y a deux caractères qui sont utilisés couramment pour réaliser la
fonction d’effacement du caractère (erroné) précédent : back space et
delete. En fonction du caractère qui est le plus facile à taper sur son
clavier, l’utilisateur désirera choisir l’un ou l’autre. Le caractère back
space peut sur tout clavier s’obtenir par Control-h, alors qu’il n’y a
pas de Control-quelque-chose correspondant au caractère delete. Selon
les claviers, il peut y avoir une touche marquée back space, et/ou une
touche marquée delete, ou une touche marquée ← qui génère back space
ou delete, et qui peut, ou ne peut pas, être configurée par le set-up du
terminal pour générer au choix back space ou delete !
Un utilisateur unix utilise la commande stty pour indiquer au sys-
tème d’exploitation le caractère qu’il désire pour réaliser la fonction
d’effacement de caractère.
258
A.2.1 Les codes ascii en octal
code 0 1 2 3 4 5 6 7
000 nul soh stx etx eot enq ack bel
010 bs ht lf vt np cr so si
020 dle dc1 dc2 dc3 dc4 nak syn etb
030 can em sub esc fs gs rs us
040 sp ! " # $ % & ’
050 ( ) * + , - . /
060 0 1 2 3 4 5 6 7
070 8 9 : ; < = > ?
100 @ A B C D E F G
110 H I J K L M N O
120 P Q R S T U V W
130 X Y Z [ \ ] ^ _
140 ‘ a b c d e f g
150 h i j k l m n o
160 p q r s t u v w
170 x y z { | } ~ del
259
A.2.2 Les codes ascii en hexadécimal
code 0 1 2 3 4 5 6 7 8 9 A B C
0x00 nul soh stx etx eot enq ack bel bs ht lf vt np
0x10 dle dc1 dc2 dc3 dc4 nak syn etb can em sub esc fs
0x20 sp ! " # $ % & ’ ( ) * + ,
0x30 0 1 2 3 4 5 6 7 8 9 : ; <
0x40 @ A B C D E F G H I J K L
0x50 P Q R S T U V W X Y Z [ \
0x60 ‘ a b c d e f g h i j k l
0x70 p q r s t u v w x y z { |
260
A.2.3 Les codes ascii en décimal
code 0 1 2 3 4 5 6 7 8 9
0 nul soh stx etx eot enq ack bel bs ht
10 lf vt np cr so si dle dc1 dc2 dc3
20 dc4 nak syn etb can em sub esc fs gs
30 rs us sp ! " # $ % & ’
40 ( ) * + , - . / 0 1
50 2 3 4 5 6 7 8 9 : ;
60 < = > ? @ A B C D E
70 F G H I J K L M N O
80 P Q R S T U V W X Y
90 Z [ \ ] ^ _ ‘ a b c
100 d e f g h i j k l m
110 n o p q r s t u v w
120 x y z { | } ~ del
261
A.3 Les codes ISO-Latin-1
octal 0 1 2 3 4 5 6 7
0000 NUL SOH STX ETX EOT ENQ ACK BEL
0010 BS HT LF VT NP CR SO SI
0200
0210
0220 ı ` ´ ˆ ˜ ¯ ˘ ˙
0230 ¨ ˚ ¸ ˝ ˛ ˇ
0240 ¡ ¢ £ ¤ ¥ ¦ §
0250 ¨ © ª « ¬ - ® ¯
0260 ° ± ² ³ ´ µ ¶ ·
0270 ¸ ¹ º » ¼ ½ ¾ ¿
0300 À Á Â Ã Ä Å Æ Ç
0310 È É Ê Ë Ì Í Î Ï
0320 Ð Ñ Ò Ó Ô Õ Ö ×
0330 Ø Ù Ú Û Ü Ý Þ ß
0340 à á â ã ä å æ ç
0350 è é ê ë ì í î ï
0360 ð ñ ò ó ô õ ö ÷
0370 ø ù ú û ü ý þ ÿ
262
Appendix B
Bibliographie
263
étant sans intéret. Il y a cependant une raison de se procurer ce livre :
obtenir au prix d’un livre le texte officiel d’une norme qui coute très cher si
on se la procure directement auprès des organismes normalisateurs.
[5] Peter Van der Linden Expert C programming. Sun Soft Press - A Pren-
tice Hall Title
Ce livre n’est ni un livre pour apprendre le langage, ni un manuel de référence
pour programmeur confirmé. C’est un mélange d’anecdotes, d’études fouil-
lées de certains points difficiles du langage, de défis de programmation, de
récit de bugs désastreux etc. Se lit davantage comme un roman que comme
un livre technique.
264
Appendix C
Ressources Internet
265
266
Appendix D
La grammaire
unité-lexicale :
⇒ mot-clé
⇒ identificateur
⇒ constante
⇒ chaîne-littérale
⇒ opérateur
⇒ ponctuation
unité-lexicale-du-pp :
⇒ nom-fichier-inclusion
⇒ identificateur
⇒ nombre-du-pp
⇒ constante-caractère
⇒ chaîne-littérale
⇒ opérateur
⇒ ponctuation
⇒ tout caractère non blanc qui ne peut être une des entités
précédentes
267
D.2 Les mots-clés
mot-clé : un parmi
auto double int struct
break else long switch
case enum register typedef
char extern return union
const float short unsigned
continue for signed void
default goto sizeof volatile
do if static while
non-chiffre : un parmi
_ a b c d e f g h i j k l m
n o p q r s t u v w x y z
A B C D E F G H I J K L M
N O P Q R S T U V W X Y Z
chiffre : un parmi
0 1 2 3 4 5 6 7 8 9
268
⇒ constante-d-énumération
⇒ constante-caractère
constante-flottante :
⇒ partie-fractionnaire partie-exposant option suffixe-flottant option
⇒ séquence-de-chiffres partie-exposant suffixe-flottant option
partie-fractionnaire :
⇒ séquence-de-chiffres option . séquence-de-chiffres
⇒ séquence-de-chiffres .
partie-exposant :
⇒ e signe option séquence-de-chiffres
⇒ E signe option séquence-de-chiffres
signe : un parmi
+ -
séquence-de-chiffres :
⇒ chiffre
⇒ séquence-de-chiffres chiffre
suffixe-flottant : un parmi
f l F L
constante-entière :
⇒ constante-décimale suffixe-entier option
⇒ constante-octale suffixe-entier option
⇒ constante-hexadécimale suffixe-entier option
constante-décimale :
⇒ chiffre-non-nul
⇒ constante-décimale chiffre
constante-octale :
⇒ 0
269
⇒ constante-octale chiffre-octal
constante-hexadécimale :
⇒ 0x chiffre-hexadécimal
⇒ 0X chiffre-hexadécimal
⇒ constante-hexadécimale chiffre-hexadécimal
chiffre-non-nul : un parmi
1 2 3 4 5 6 7 8 9
chiffre-octal : un parmi
0 1 2 3 4 5 6 7
chiffre-hexadécimal : un parmi
0 1 2 3 4 5 6 7 8 9
a b c d e f A B C D E F
suffixe-entier :
⇒ suffixe-non-signé suffixe-long option
⇒ suffixe-long suffixe-non-signé option
suffixe-non-signé : un parmi
u U
suffixe-long : un parmi
l L
constante-d-énumération :
⇒ identificateur
constante-caractère :
⇒ ’ séquence-de-caractères-c ’
⇒ L’ séquence-de-caractères-c ’
séquence-de-caractères-c :
⇒ caractère-c
270
⇒ séquence-de-caractères-c caractère-c
caractère-c :
⇒ n’importe quel membre du jeu de caractères source sauf le quote
(’)
le backslash (\) ou le newline
⇒ séquence-d-échappement
séquence-d-échappement :
⇒ séquence-d-échappement-simple
⇒ séquence-d-échappement-octale
⇒ séquence-d-échappement-hexadécimale
séquence-d-échappement-simple : un parmi
\’ \" \? \\
\a \b \f \n \r \t \v
séquence-d-échappement-octale :
⇒ \ chiffre-octal
⇒ \ chiffre-octal chiffre-octal
⇒ \ chiffre-octal chiffre-octal chiffre-octal
séquence-d-échappement-hexadécimale :
⇒ \x chiffre-hexadécimal
⇒ séquence-d-échappement-hexadécimale chiffre-hexadécimal
séquence-de-caractères-s :
⇒ caractère-s
271
⇒ séquence-de-caractères-s caractère-s
caractère-s :
⇒ n’importe quel membre du jeu de caractères source sauf le double
quote (")
le backslash (\) ou le newline
⇒ séquence-d-échappement
D.7 La ponctuation
ponctuation : un parmi
[ ] ( ) { } * , : = ; ... #
séquence-de-caractères-h :
⇒ caractère-h
⇒ séquence-de-caractères-h caractère-h
272
caractère-h :
⇒ n’importe quel membre du jeu de caractères source sauf > ou le
newline
séquence-de-caractères-q :
⇒ caractère-q
⇒ séquence-de-caractères-q caractère-q
caractère-q :
⇒ n’importe quel membre du jeu de caractères source sauf " ou le
newline
expression-postfixée :
273
⇒ expression-primaire
⇒ expression-postfixée [ expression ]
⇒ expression-postfixée ( liste-d-expressions-paramètres option )
⇒ expression-postfixée . identificateur
⇒ expression-postfixée -> identificateur
⇒ expression-postfixée ++
⇒ expression-postfixée –
liste-d-expressions-paramètres :
⇒ expression-affectation
⇒ liste-d-expressions-paramètres , expression-affectation
expression-unaire :
⇒ expression-postfixée
⇒ ++ expression-unaire
⇒ – expression-unaire
⇒ opérateur-unaire expression-cast
⇒ sizeof expression-unaire
⇒ sizeof ( nom-de-type )
opérateur-unaire : un parmi
& * + - ~ !
expression-cast :
⇒ expression-unaire
⇒ ( nom-de-type ) expression-cast
expression-multiplicative :
⇒ expression-cast
⇒ expression-multiplicative * expression-cast
⇒ expression-multiplicative / expression-cast
⇒ expression-multiplicative % expression-cast
expression-additive :
⇒ expression-multiplicative
⇒ expression-additive + expression-multiplicative
274
⇒ expression-additive - expression-multiplicative
expression-décalage :
⇒ expression-additive
⇒ expression-décalage « expression-additive
⇒ expression-décalage » expression-additive
expression-relation :
⇒ expression-décalage
⇒ expression-relation < expression-décalage
⇒ expression-relation > expression-décalage
⇒ expression-relation <= expression-décalage
⇒ expression-relation >= expression-décalage
expression-égalité :
⇒ expression-relation
⇒ expression-égalité == expression-relation
⇒ expression-égalité != expression-relation
expression-et :
⇒ expression-égalité
⇒ expression-et & expression-égalité
expression-ou-exclusif :
⇒ expression-et
⇒ expression-ou-exclusif ^ expression-et
expression-ou-inclusif :
⇒ expression-ou-exclusif
⇒ expression-ou-inclusif | expression-ou-exclusif
expression-et-logique :
⇒ expression-ou-inclusif
⇒ expression-et-logique && expression-ou-inclusif
expression-ou-logique :
275
⇒ expression-et-logique
⇒ expression-ou-logique || expression-et-logique
expression-conditionnelle :
⇒ expression-ou-logique
⇒ expression-ou-logique ? expression : expression-
conditionnelle
expression-affectation :
⇒ expression-conditionnelle
⇒ expression-unaire opérateur-affectation expression-affectation
opérateur-affectation : un parmi
= *= /= %= += -= <<= >>= &= ^= |=
expression :
⇒ expression-affectation
⇒ expression , expression-affectation
expression-constante :
⇒ expression-conditionnelle
spécificateurs-de-déclaration :
⇒ spécificateur-de-classe-mémoire spécificateurs-de-déclaration option
⇒ spécificateur-de-type spécificateurs-de-déclaration option
⇒ qualificatif-de-type spécificateurs-de-déclaration option
liste-de-déclarateurs-init :
⇒ déclarateur-init
276
⇒ liste-de-déclarateurs-init , déclarateur-init
déclarateur-init :
⇒ déclarateur
⇒ déclarateur = initialisateur
spécificateur-de-classe-mémoire :
⇒ auto
⇒ extern
⇒ static
⇒ register
⇒ typedef
spécificateur-de-type :
⇒ void
⇒ char
⇒ short
⇒ int
⇒ long
⇒ float
⇒ double
⇒ signed
⇒ unsigned
⇒ spécificateur-de-struct-ou-union
⇒ spécificateur-d-énumération
⇒ nom-de-typedef
spécificateur-de-struct-ou-union :
⇒ struct-ou-union identificateur option { liste-de-déclarations-de-
struct }
⇒ struct-ou-union identificateur
struct-ou-union :
⇒ struct
⇒ union
277
liste-de-déclarations-de-struct :
⇒ déclaration-de-struct
⇒ liste-de-déclarations-de-struct déclaration-de-struct
déclaration-de-struct :
⇒ liste-de-spécificateurs-et-qualificatifs liste-de-déclarateurs-de-
struct ;
liste-de-spécificateurs-et-qualificatifs :
⇒ spécificateur-de-type liste-de-spécificateurs-et-qualificatifs option
⇒ qualificatif-de-type liste-de-spécificateurs-et-qualificatifs option
liste-de-déclarateurs-de-struct :
⇒ déclarateur-de-struct
⇒ liste-de-déclarateurs-de-struct , déclarateur-de-struct
déclarateur-de-struct :
⇒ déclarateur
⇒ déclarateur option : expression-constante
spécificateur-d-énumération :
⇒ enum identificateur option { liste-d-énumérateurs }
⇒ enum identificateur
liste-d-énumérateurs :
⇒ énumérateur
⇒ liste-d-énumérateurs , énumérateur
énumérateur :
⇒ constante-d-énumération
⇒ constante-d-énumération = expression-constante
qualificatif-de-type :
⇒ const
⇒ volatile
278
déclarateur :
⇒ pointeur option déclarateur-direct
déclarateur-direct :
⇒ identificateur
⇒ ( déclarateur )
⇒ déclarateur-direct [ expression-constante option ]
⇒ déclarateur-direct ( liste-de-types-de-paramètres )
⇒ déclarateur-direct ( liste-d-identificateurs option )
pointeur :
⇒ * liste-de-qualificatifs-de-types option
⇒ * liste-de-qualificatifs-de-types option pointeur
liste-de-qualificatifs-de-types :
⇒ qualificatif-de-type
⇒ liste-de-qualificatifs-de-types qualificatif-de-type
liste-de-types-de-paramètres :
⇒ liste-de-paramètres
⇒ liste-de-paramètres , ...
liste-de-paramètres :
⇒ déclaration-de-paramètre
⇒ liste-de-paramètres , déclaration-de-paramètre
déclaration-de-paramètre :
⇒ spécificateurs-de-déclaration déclarateur
⇒ spécificateurs-de-déclaration déclarateur-abstrait option
liste-d-identificateurs :
⇒ identificateur
⇒ liste-d-identificateurs , identificateur
nom-de-type :
⇒ liste-de-spécificateurs-et-qualificatifs déclarateur-abstrait option
279
déclarateur-abstrait :
⇒ pointeur
⇒ pointeur option déclarateur-abstrait-direct
déclarateur-abstrait-direct :
⇒ ( déclarateur-abstrait )
⇒ déclarateur-abstrait-direct option [ expression-constante option ]
⇒ déclarateur-abstrait-direct option ( liste-de-types-de-
paramètres option )
nom-de-typedef :
⇒ identificateur
initialisateur :
⇒ expression-affectation
⇒ { liste-d-initialisateurs }
⇒ { liste-d-initialisateurs , }
liste-d-initialisateurs :
⇒ initialisateur
⇒ liste-d-initialisateurs , initialisateur
instruction-étiquettée :
280
⇒ identificateur : instruction
⇒ case expression-constante : instruction
⇒ default : instruction
instruction-composée :
⇒ { liste-de-déclarations option liste-d-instructions option }
liste-de-déclarations :
⇒ déclaration
⇒ liste-de-déclarations déclaration
liste-d-instructions :
⇒ instruction
⇒ liste-d-instructions instruction
instruction-expression :
⇒ expression option ;
instruction-sélection :
⇒ if ( expression ) instruction
⇒ if ( expression ) instruction else instruction
⇒ switch ( expression ) instruction
instruction-itération :
⇒ while ( expression ) instruction
⇒ do instruction while ( expression ) ;
⇒ for ( expression option ; expression option ; expression option
)
instruction
instruction-saut :
⇒ goto identificateur ;
⇒ continue ;
⇒ break ;
⇒ return expression option ;
281
D.13 Définitions externes
unité-de-compilation :
⇒ déclaration-externe
⇒ unité-de-compilation déclaration-externe
déclaration-externe :
⇒ définition-de-fonction
⇒ déclaration
définition-de-fonction :
⇒ spécificateurs-de-déclaration option déclarateur liste-de-
déclarations option
instruction-composée
groupe :
⇒ partie-de-groupe
⇒ groupe partie-de-groupe
partie-de-groupe :
⇒ liste-d-unités-lexicales-du-ppoption newline
⇒ section-if
⇒ ligne-directive
section-if :
⇒ groupe-if liste-de-groupes-elif option groupe-else option ligne-endif
groupe-if :
⇒ # if expression-constante newline groupe option
282
⇒ # ifdef identificateur newline groupe option
⇒ # ifndef identificateur newline groupe option
liste-de-groupes-elif :
⇒ groupe-elif
⇒ liste-de-groupes-elif groupe-elif
groupe-elif :
⇒ # elif expression-constante newline groupe option
groupe-else :
⇒ # else newline groupe option
ligne-endif :
⇒ # endif newline
ligne-directive :
⇒ # include liste-d-unités-lexicales-du-pp newline
⇒ # define identificateur remplacement newline
⇒ # define identificateur parenthèse-g liste-d-
identificateurs option
remplacement newline
⇒ # undef identificateur newline
⇒ # line liste-d-unités-lexicales-du-pp newline
⇒ # error liste-d-unités-lexicales-du-ppoption newline
⇒ # pragma liste-d-unités-lexicales-du-ppoption newline
⇒ # newline
parenthèse-g :
⇒ le caractère ( non précédé d’un espace blanc
remplacement :
⇒ liste-d-unités-lexicales-du-ppoption
liste-d-unités-lexicales-du-pp :
⇒ unité-lexicale-du-pp
283
⇒ liste-d-unités-lexicales-du-pp unité-lexicale-du-pp
newline :
⇒ le caractère séparateur de lignes
284
D.15 Références croisées de la grammaire
caractère-c 27 26
caractère-h 39 38
caractère-q 41 40
caractère-s 34 33
chaîne-littérale 2 1, 2, 43
chiffre 6 4, 42
chiffre-hexadécimal 20 17, 31
chiffre-non-nul 18 15
chiffre-octal 19 16, 30
constante 7 1, 43
constante-caractère 25 2, 7
constante-d-énumération 24 7, 79
constante-décimale 15 14, 15
constante-entière 14 7
constante-flottante 87
constante-hexadécimale 17 14, 17
constante-octale 16 14
déclarateur 81 67, 76, 82 87, 106
déclarateur-abstrait 90 87, 89, 91
déclarateur-abstrait-direct 91 90, 91
déclarateur-de-struct 76 75
déclarateur-direct 82 81, 82
déclarateur-init 67 66
déclaration 64 98, 105
déclaration-de-paramètre 87 86
déclaration-de-struct 73 72
déclaration-externe 105 104
définition-de-fonction 106 105
énumérateur 79 78
expression 62 43, 44, 59, 62, 100, 101, 102, 103
expression-additive 50 50, 51
285
expression-affectation 60 45, 60, 62, 93
expression-cast 48 46, 48, 49
expression-conditionnelle 59 59, 60, 63
expression-constante 63 76, 79, 82, 91, 96, 111, 113
expression-décalage 51 51, 52
expression-égalité 53 53, 54
expression-et 54 54, 55
expression-et-logique 57 57, 58
expression-multiplicative 49 49, 50
expression-ou-exclusif 55 55, 56
expression-ou-inclusif 56 56, 57
expression-ou-logique 58 58, 59
expression-postfixée 44 44, 46
expression-primaire 43 44
expression-relation 52 52, 53
expression-unaire 46 46, 48, 60
fichier-du-pp 107
groupe 108 107, 108, 111, 113, 114
groupe-elif 113 112
groupe-else 114 110
groupe-if 111 110
identificateur 4 1, 2, 4, 24, 43, 44, 70, 77, 82 88, 92, 96, 1
initialisateur 93 67, 94
instruction 95 96, 99, 101, 102
instruction-composée 97 95, 106
instruction-étiquettée 96 95
instruction-expression 100 95
instruction-itération 102 95
instruction-saut 103 95
instruction-sélection 101 95
ligne-directive 116 109
ligne-endif 115 110
286
liste-d-énumérateurs 78 77, 78
liste-de-déclarateurs-de-struct 75 73, 75
liste-de-déclarateurs-init 66 64, 66
liste-de-déclarations 98 97, 98, 106
liste-de-déclarations-de-struct 72 70, 72
liste-de-groupes-elif 112 110, 112
liste-de-paramètres 86 85, 86
liste-de-qualificatifs-de-types 84 83, 84
liste-de-spécificateurs-et- 74 73, 74, 89
qualificatifs
liste-de-types-de-paramètres 85 82, 91
liste-d-expressions-paramètres 45 44, 45
liste-d-identificateurs 88 82, 88, 116
liste-d-initialisateurs 94 93, 94
liste-d-instructions 99 97, 99
liste-d-unités-lexicales-du-pp 119 109, 116, 118, 119
mot-clé 31
newline 120 27, 109, 111, 113, 114, 115, 116
nom-de-type 89 46, 48
nom-de-typedef 92 69
nom-fichier-inclusion 37 2
nombre-du-pp 42 2, 42
non-chiffre 5 4, 15, 42
opérateur 5 1, 2
opérateur-affectation 61 60
opérateur-unaire 47 46
parenthèse-g 117 116
partie-de-groupe 109 108
partie-exposant 10 8
partie-fractionnaire 98
pointeur 83 81, 83, 90
ponctuation 36 1, 2
287
qualificatif-de-type 80 65, 74, 84
remplacement 118 116
section-if 110 109
séquence-de-caractères-c 26 25, 26
séquence-de-caractères-h 38 37, 38
séquence-de-caractères-q 40 37, 40
séquence-de-caractères-s 33 32, 33
séquence-d-échappement 28 27, 34
séquence-d-échappement- 31 28, 31
hexadécimale
séquence-d-échappement-octale 30 28
séquence-d-échappement-simple 29 28
séquence-de-chiffres 12 8, 9, 10
signe 11 10, 42
spécificateur-d-énumération 77 69
spécificateur-de-classe-mémoire 68 65
spécificateur-de-struct-ou-union 70 69
spécificateur-de-type 69 65, 74
spécificateurs-de-déclaration 65 64, 65, 87, 106
struct-ou-union 71 70
suffixe-entier 21 14
suffixe-flottant 13 8
suffixe-long 23 21
suffixe-non-signé 22 21
unité-de-compilation 104 104
unité-lexicale 1
unité-lexicale-du-pp 2 119
fin des références croisées de la grammaire
288
Appendix E
Un bestiaire de types
289
formes équivalentes forme préférée sémantique
short
short int
short int entier court
signed short
signed short int
int
signed int entier
signed int
long
long int
long int entier long
signed long
signed long int
unsigned short
unsigned short int unsigned short int entier court non si
unsigned
unsigned int unsigned int entier non signé
unsigned long
unsigned long int unsigned long int entier long non sig
char caractère
signed char caractère signé
unsigned char caractère non signé
float flottant simple pré
double flottant double pré
long double flottant quadruple
290
int tab[10] ; tableau de 10 entiers
int tab[5] = { 10, 11, 12, 13, 14}; tableau de 5 entiers avec initialisa
char mes[80]; tableau de 80 caractères
char mess[] = "Erreur de syntaxe"; tableau de caractères avec initialis
int t[24][80]; tableau à deux dimensions
int t1[2][3] = {
{0, 1, 2},
{ 10, 11, 12} tableau à deux dimensions avec in
};
292
void f(void)
{ fonction sans paramètre et sans valeur
/* corps de la fonction */ retournée
}
void f(int a)
{ fonction avec un paramètre entier, sans
/* corps de la fonction */ valeur retournée
}
int f(void)
{ fonction sans paramètre retournant un
/* corps de la fonction */ int
}
int f(int a, float b)
{ fonction avec un paramètre entier et un
/* corps de la fonction */ paramètre flottant, retournant un int
}
int f(int a, ...)
fonction avec un paramètre entier et un
{
nombre variable de paramètres, retour-
/* corps de la fonction */
nant un int
}
enum coul {
bleu, blanc, rouge bleu = 0, blanc = 1 et rouge = 2
};
enum coul {
bleu, blanc=10, rouge bleu = 0, blanc = 10 et rouge = 11
};
enum coul {
bleu=3,
blanc=5, bleu = 3, blanc = 5 et rouge = 9
rouge=9
};
293
294
E.6 Les structures, unions et champs de bits
struct complex {
/* partie réelle */
Structure à deux champs flottants.
float x;
Définit l’étiquette de structure
/* partie imaginaire */
complex sans déclarer de variable.
float y;
};
struct complex {
/* partie réelle */ Structure à deux champs flottants.
float x; Définit l’étiquette de structure
/* partie imaginaire */ complex et déclare les deux variables
float y; c1 et c2.
} c1, c2;
struct {
/* partie réelle */
Structure à deux champs flottants.
float x;
Déclare la variable c sans définir
/* partie imaginaire */
d’étiquette pour la structure.
float y;
} c;
enum type {ENTIER, FLOTTANT}
struct arith {
enum type typ_val; Union d’un type entier et d’un type
union flottant associé dans une structure à un
{ indicateur (le champ typ_val) perme-
int i; ttant de connaitre le type de la valeur
float f; stockée dans l’union u.
} u;
};
struct sr {
unsigned int trace : 2;
unsigned int priv : 2;
unsigned int : 1;
unsigned int masque : 3; Champs de bits : description du reg-
unsigned int : 3; istre d’état du mc68000. Il s’agit d’un
unsigned int extend : 1; mot de 16 bits. Les troisième et cin-
unsigned int negative : 1; quième champs ne portent pas de nom.
unsigned int zéro : 1;
unsigned int overflow : 1;
unsigned int carry : 1; 295
E.7 Les qualificatifs
Il y a deux qualificatifs : const et volatile Les exemples présentés sont avec
const, l’utilisation de volatile étant rigoureusement identique.
const int i; Entier constant
const int *p; Pointeur vers un entier constant.
int * const p; Pointeur constant vers un entier.
const int * const p; Pointeur constant vers un entier con-
stant.
Tableau constant : tous les éléments de
const int t[5];
t sont constants.
struct coord {
int x; int y;
Structure constante : tous les champs
};
de s sont constants.
const struct coord s;
296
Appendix F
Le bêtisier
Cette annexe est une collection des bêtises qu’il faut faire au moins une fois
dans sa vie pour être vacciné. La caractéristique de beaucoup de ces erreurs
est de ne pas provoquer de message d’erreur du compilateur, rendant ainsi
leur détection difficile. La différence entre le texte correct et le texte erroné
est souvent seulement d’un seul caractère. La découverte de telles erreurs ne
peut donc se faire que par un examen très attentif du source du programe.
297
F.1.2 Erreur sur l’affectation
C’est le pendant de l’erreur précédente.
Ce que voulait le programmeur Affecter b à a
Ce qu’il aurait dû écrire a = b;
Ce qu’il a écrit a == b;
La comparaison de a à b, suivie
Ce qu’il a obtenu
de l’inutilisation du résultat.
Pour que cela ait un sens, il faut que l’expression réalise un effet de bord,
mais rien ne l’impose dans la définition du langage.
298
F.2.2 Un #define n’est pas une initialisation
Ce que le programmeur a écrit Ce qu’il aurait dû écrire
#define MAX = 10 #define MAX 10
299
Ce que le programmeur a écrit Ce qu’il aurait dû écrire
if ( a > b) ; if ( a > b)
a = b; a = b;
#ifdef NOTDEFINED
...
#endif
300
F.5 Erreurs avec les priorités des opérateurs
Les priorités des opérateurs sont parfois surprenantes. Les cas les plus gênants
sont les suivants :
• La priorité des opérateurs bit à bit est inférieure à celle des opérateurs
de comparaison.
301
F.6.2 Erreur sur le default
L’alternative à exécuter par défaut est introduite par l’étiquette default. Si
une faute de frappe est commise sur cette étiquette, l’alternative par défaut
ne sera plus reconnue : l’étiquette sera prise pour une étiquette d’instruction
sur laquelle ne sera fait aucun goto.
switch(a)
{
case 1 : a = b;
defult : return(1); /* erreur non détectée */
}
Une version diabolique de cette erreur est relatée dans le livre de Peter
Van Der Linden : si la lettre l de default est remplacée par le chiffre 1, avec
les fontes utilisées pour imprimer les sources, qui verra la différence entre l
et 1 ?
302
et d’utiliser comme déclaration de référence dans une autre unité de compi-
lation :
Rappelons que int tab[] et int *t ne sont équivalents que dans le seul
cas de paramètre formel de fonction. Dans le cas qui nous occupe ici, la
déclaration de référence correcte est :
303
304
Glossaire
booléen Général ; Anglais : boolean. type dont l’ensemble des valeurs est
formé des valeurs vrai et faux.
305
compilation séparée Général ; Anglais : separate compilation. Technique
qui consiste à découper les gros programmes en différentes unités de
compilation pour en maîtriser la complexité. Les différentes unités sont
compilées séparément, et un éditeur de liens est chargé de transformer
les modules objets en programme exécutable.
durée de vie Général ; Anglais : lifetime. Concept qui s’applique aux vari-
ables. La durée de vie d’une variable est le temps qui s’écoule entre le
moment où on alloue de la mémoire pour cette variable, et le moment
où on récupère la mémoire allouée. Il existe classiquement trois types
de durée de vie :
306
effet de bord Général ; Anglais : side effect. Modification de l’état de la
machine. Un effet de bord peut être interne au programme (par exem-
ple, modification de la valeur d’une variable) ou externe au programme
(par exemple écriture dans un fichier). Toute partie de programme qui
n’est pas déclarative a pour but soit de calculer une valeur, soit de faire
un effet de bord. Voir aussi : procédure, fonction.
307
identificateur Général ; Anglais : identifier. Synonyme de nom. Dans le
domaine des langage de programmation, on parle d’identificateur pour
dire nom.
pile Général ; Anglais : stack. Dans un langage récursif, les variables locales
à une procédure doivent être allouées dans une pile, car à un moment
donné il peut y avoir plusieurs activations en cours de la même procé-
dure, et donc plusieurs instanciations des variables de la procédure.
Voir aussi : durée de vie, tas.
pointeur Général ; Anglais : pointer. Type dont les valeurs possibles sont
des adresses de variable.
308
portée Général ; Anglais : scope. Ce concept s’applique aux identificateurs.
La portée d’un identificateur est la partie du programme où l’ensemble
des occurences d’un identificateur font référence à la même déclaration.
Ce sont les blocs qui permettent de limiter la portée d’un identificateur.
tas Général ; Anglais : heap. Zone de la mémoire où sont effectuées les al-
locations dynamiques de variables explicitement demandées par le pro-
grammeur. Dans le langage C, les allocations et les libérations dans
le tas se font par des fonctions de la bibliothèque standard : calloc
et malloc pour les allocations, free pour les libérations. Voir aussi :
durée de vie, pile.
309
type de base Général ; Anglais : basic type. type qui est connu du langage,
par opposition aux types définis par le programmeur. Classiquement,
les types de base sont booléen, caractère, entier, flottant. Le langage
C90 n’a pas le type booléen, mais ce type existe en C99.
type entier Jargon C ; Anglais : integral type. Regroupe les types suivants :
- les char
- toutes les variétés de int : signés, non signés, longs ou courts.
- les types énumérés (définis par enum).
310
Index
311
mise en oeuvre du, 38, 197, defined, 205
205 définition, 209
version, 5 d’étiquette d’énumération,
compilation conditionnelle, 171
203 d’étiquette d’union, 172
complément à 2, 12, 176 d’étiquette de branchement,
const, 99, 227 84
constante, 18 d’étiquette de structure,
caractère, 14 151
chaîne de caractères, see de constante, 18
chaîne littérale de fonction, 28
décimale, 13 dépendance de
entière, 13 l’implémentation, 12,
flottante, 16 23, 28, 170
hexadécimale, 13 do, see instruction, do
nommée, 18 double, 13, 131, 139, 180
octale, 13 durée de vie, 216
continue, see instruction,
continue éditeur de liens, 8, 38, 219,
conversion, 21 221
arithmétiques habituelles, effet de bord, 20, 24, 32, 51,
180 55, 138
de chaîne littérale, 100 else, 26
de tableau, 93, 161 entier, see type, entier
de types, 175 entrée standard, 80
cpp, 6 entrées-sorties, 35
enum, 18, 171
déclaration, 25, 36, 209 énumération, 18, 171
d’union, 172, 211 EOF, 123, 125, 129, 138
de fonction, 28, 33, 223 EOF, 80
de pointeur, 63 espace de noms, 214
de structure, 151, 211 étiquette, 84
de tableau, 47, 104 d’union, 172, 211
de variable, 19, 29 de structure, 152, 211
portée de, 211 évaluation des opérandes, 21
default, 81 exit, 38
312
exposant, 16 de fonctions, 33
extern, 33, 34, 188, 210, 219, initialisation
222 de structure, 153
de tableau, 48, 105
fclose, 122 de variables simples, 19
fgetc, 123 instruction
fgets, 126 break, 53, 81, 82
fichier d’inclusion, 27 composée, 25
FILE, 121, 128 continue, 54
float, 13, 131, 139, 180 do, 52, 203
flottant, see type, flottant expression, 24, 56
fonction
for, 50, 85
appel de, 31
goto, 84
définition, 28
if, 26
externe, 33
nulle, 85
prototype de , see
return, 29, 32, 38
prototype, de fonction
switch, 81
récursive, 33, 210
while, 52
fopen, 119
int, 12, 24
for, see instruction, for
INT16_MAX, 251
fprintf, 130
INT16_MIN, 251
fputc, 125
int16_t, 251
fputs, 128
INT32_MAX, 251
free, 163
INT32_MIN, 251
fscanf, 138
int32_t, 251
fseek, 121
INT64_MAX, 251
getc, 124 INT64_MIN, 251
getchar, 124 int64_t, 251
gets, 127 INT8_MAX, 251
goto, see instruction, goto INT8_MIN, 251
int8_t, 251
identificateur, 8, 18, 28, 151 iso, 5
if, see instruction, if
imbrication k&r, 5, 30, 34, 64
de blocs, 213
de commentaires, 9 libération de mémoire, 161
313
linux, 199 /, 22
little endian, 170, 188 <, 24
long, 12, 13, 131, 139, 180 <=, 24
lvalue, 20, 50, 54, 55 <<, 185
=, 20, 24
macro, 195
==, 24
avec paramètres, 198
>, 24
corps de, 198
>=, 24
expansion de, 195, 198
prédéfinie, 197 >>, 185
sans paramètre, 195 ?:, 186
main, 36, 38 %, 23
malloc, 64, 161 &
mantisse, 16 adresse de, 64, 74, 81
membre et bit à bit, 184
de structure, 152 &&, 57
accès aux, 159, 174 ^, 184
mot-clé, 7 || (ou logique), 58
~(tilde), 183
nombre adresse de, see opérateur &
entier, 12 affectation, see opérateur
flottant, 13 =
NULL, 119, 121, 127, 178 affectation composée, 187
opérateur conversion, 188
!, 58 d’adressage, 190
!=, 24 de comparaison, 23
* décrémentation, see
indirection, 65 opérateur –
multiplication, 22 et logique, see opérateur
+, 21 &&
++, 54 incrémentation, see
,(virgule), 186 opérateur ++
-, 21 indexation, 50, 94
–, 55 indirection, see opérateur
->, 159 *
.(point), 154 modulo, see opérateur %
314
ou logique, see opérateur priorité
|| opérateur, 191
sizeof, 160, 181, 188 procédure, see fonction
sur les structures, 154, programme, 36
159 promotion des entiers, 178
| (ou bit a bit), 184 prototype
ordre de fonction, 29
d’évaluation, 192 ptrdiff_t, 69
putc, 126
paramètre putchar, 126
de programme, 113 puts, 129
effectif, 31
formel, 29 qualificatif de type, 227
nombre variable de, 228
récursivité
passage de, 73
de fonction, see fonction,
par adresse, 73
récursive
par valeur, 73
de structure, see
passage de structure en,
structure, récursive
see structure, passage
register, 218, 219
en paramètre
return, see instruction,
passage de tableau en, see
return
tableau, passage en
paramètre scanf, 80, 145
pointeur séquence d’échappement, 14,
arithmétique sur, 68 17, 35, 80, 130, 139
concept de, 63 shell, 38, 116, 192, 196
conversion d’un, 177 short, 12, 131, 139, 179
conversion vers, 178 signed, 170
et opérateurs ++ et –, 69 sizeof , see operateur, sizeof
et opérateurs + et -, 68 sprintf, 135
et tableau, 93 sscanf, 145
générique, 64, 188 static, 216, 219
invalide, see NULL stderr, 122
vers une structure, 158 stdin, 122
préprocesseur, 6, 18, 119, 174 stdout, 122
printf, 35, 101, 135 struct, 152, 211
315
structure, 211 taille de, 47
affectation, see type, 224
affectation, de booléen, 250
structure caractère, 12
déclaration, see entier, 12, 139, 177
déclaration, de entier de taille fixée, 251
structure flottant, 13, 139, 177
initialisation, see qualificatif de, 227
initialisation, de typedef, 219, 224
structure
opérateurs sur, see union, 172, 211
opérateurs, sur les unité de compilation, 9, 36,
structures 221
passage en paramètre, 159 unité lexicale, 7
récursive, 159 unsigned, 12, 13, 131, 139,
surcharge 169, 179–181, 185
d’opérateur, 175 va_arg, 228
de *, 65 va_end, 228
de break, 84 va_list, 228
switch, see instruction, va_start, 228
switch variable
globale, 36
tableau locale, 29, 36, 76
de caractères, 49, 50 version, 5
de pointeurs, 109 visibilité, 213
de structure, 154 void, 28, 30–32, 64, 131, 178,
déclaration, 47 188
élément de, 50, 104 volatile, 227
et pointeur, 93
généralité, 93 while, see instruction, while
initialisation, see
initialisation de
tableau
multidimensionnel, 104
passage en paramètre, 96,
98, 104
316