Académique Documents
Professionnel Documents
Culture Documents
environnement
Versions C90 et C99
Jean-Paul R IGAULT
V 2.1 – 7 février 2010
C onçu à la fin des années 1960 par Dennis Ritchie, le langage C s’est imposé
comme une référence en matière de programmation-système et de réalisa-
tion de logiciel de base. Ceci est bien entendu lié pour partie au succès d’Unix,
système d’exploitation pour lequel C a été conçu. Mais c’est aussi le résultat
des vertus intrinsèques du langage : suffisamment simple pour être efficace et
portable, suffisamment moderne pour être expressif.
Cette origine de langage de programmation-système a longtemps conféré
à C la réputation d’un langage « laxiste », dans lequel le typage n’était qu’une
contrainte surajoutée, presque décorative. Les habitudes et les exigences de
programmation aidant, il était nécessaire de rendre plus rigoureux l’outil. C’est
à cette tâche que s’est consacré le comité de normalisation x3j11 de l’ansi [2].
Le résultat fut un langage plus sûr, doté du typage fort garant d’une certaine
sécurité de programmation, sans que cela nuise à l’efficacité des programmes
générés.
C’est ce langage, dit ansi C, adopté par l’ansi en 1989 puis par l’iso en
1990 (iso/iec 9899:1990) qui est actuellement la norme de fait. Nous le dési-
gnerons simplement par C90. Cette norme a subi quelques révisions mineures
jusqu’à ce que l’évolution parallèle de la normalisation du langage C++ oblige
à une reprise profonde de la norme. Ceci à conduit en 1999, à la norme iso/iec
9899:1999 reprise par l’ansi en 2000. Le nouveau langage, dit C99, n’est pas
encore à ce jour (janvier 2010) complètement implémenté par tous les compila-
teurs, bien qu’un large sous-ensemble le soit généralement.
Le présent texte décrit à la fois iso C90 et iso C99, en signalant les diffé-
rences entre les deux versions. Ces différences sont résumées dans l’annexe 12.
Sauf mention explicite du contraire, les programmes proposés sont compatibles
avec les deux versions. Pour les détails, voir la section 1.5.
Tous les exemples de cet ouvrage ont été développés et testés dans un envi-
ronnement tout à fait habituel dans les établissements d’enseignement et de re-
cherche : stations de travail sous Linux, environnement graphique X Window.
Le compilateur ansi C utlisé comme référence est celui de gnu dû initiale-
ment à Richard Stallman (et à la Free Software Foundation), à savoir gcc [1, 23].
Les programmes devraient être compilables avec les versions de gcc à partir
de 2.95 au moins mais c’est seulement à partir des versions 3.x que l’on a la
compatibilité avec C99 (option -std=c99).
La compilation s’effectue sous le contrôle de make (version de gnu là en-
core). La mise au point utilise gdb [19], autre produit de la Free Software Foun-
dation, utilisé à travers l’une de ses interfaces avec X Window (principalement
ddd [20]). Ont également été utilisés les analyseurs d’exécution gprof [21] et
Jean-Paul Rigault
Professeur d’informatique
Université de Nice Sophia Antipolis
Sophia Antipolis et La Roquette sur Siagne
Février 1993–Janvier 2010
c Jean-Paul R IGAULT
V 2.1 – 7 février 2010
Table des matières
Avant-propos 1
1 Introduction 17
1.1 Historique . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
1.2 Caractéristiques de C . . . . . . . . . . . . . . . . . . . . . . . . 18
1.2.1 Un langage contemporain de Pascal . . . . . . . . . . 18
1.2.2 Un langage d’implémentation de systèmes . . . . . . . 18
1.2.3 Un langage supportant la compilation séparée . . . . . 18
1.2.4 Un langage incomplet. . . pour être portable . . . . . . 18
1.3 Plan . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19
1.4 Notations utilisées . . . . . . . . . . . . . . . . . . . . . . . . . 19
1.5 Compilateurs et compatibilité . . . . . . . . . . . . . . . . . . . 20
1.5.1 Environnement de développement de référence . . . . 20
1.5.2 Version du langage C . . . . . . . . . . . . . . . . . . . 21
1.6 Ressources : ouvrages et sites Web sur C . . . . . . . . . . . . 21
1.7 Exercices du chapitre 1 . . . . . . . . . . . . . . . . . . . . . . . 22
2 Premiers pas 25
2.1 Monde, salut ! . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
2.2 Boucle while ; entrées-sorties simples . . . . . . . . . . . . . . 27
2.3 Fonctions ; expressions ; entrées-sorties . . . . . . . . . . . . . 29
2.3.1 Exemple : calcul de la racine carrée d’un réel . . . . . . 29
2.3.2 Entrées-sorties formattées . . . . . . . . . . . . . . . . . 33
2.4 Tableaux et instruction d’itération . . . . . . . . . . . . . . . . 35
2.5 Arguments du shell ; fonction récursive . . . . . . . . . . . . . 38
2.6 Exercices du chapitre 2 . . . . . . . . . . . . . . . . . . . . . . . 41
3 Bases du langage 43
3.1 Eléments lexicaux . . . . . . . . . . . . . . . . . . . . . . . . . . 43
3.1.1 Jeu de caractères . . . . . . . . . . . . . . . . . . . . . . 43
3.1.2 Structure lexicale d’un fichier-source . . . . . . . . . . 44
3.1.3 Commentaires . . . . . . . . . . . . . . . . . . . . . . . . 45
3.1.4 Identificateurs . . . . . . . . . . . . . . . . . . . . . . . . 45
3.1.5 Mots-clés . . . . . . . . . . . . . . . . . . . . . . . . . . . 46
3.1.6 Constantes littérales arithmétiques . . . . . . . . . . . . 46
3.1.7 Chaînes de caractères littérales . . . . . . . . . . . . . . 48
3.2 Types scalaires et déclarations simples . . . . . . . . . . . . . . 50
3.2.1 Panorama des types de C . . . . . . . . . . . . . . . . . 50
3.2.2 Type vide (void) . . . . . . . . . . . . . . . . . . . . . . 50
3.2.3 Types de base entiers . . . . . . . . . . . . . . . . . . . . 51
3.2.4 Types réels . . . . . . . . . . . . . . . . . . . . . . . . . . 53
3.2.5 Définitions d’objets de type de base scalaire . . . . . . 53
3.2.6 Types énumérés . . . . . . . . . . . . . . . . . . . . . . . 54
3.2.7 Synonymie de types : typedef . . . . . . . . . . . . . 55
3.3 Opérateurs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 56
3.3.1 Opérateurs arithmétiques . . . . . . . . . . . . . . . . . 56
3.3.2 Opérateurs relationnels et logiques . . . . . . . . . . . 58
3.3.3 Opérateurs bit à bit . . . . . . . . . . . . . . . . . . . . . 60
3.3.4 Affectations . . . . . . . . . . . . . . . . . . . . . . . . . 63
3.3.5 Opérateurs sur les pointeurs . . . . . . . . . . . . . . . 64
3.3.6 Opérateurs sur les types . . . . . . . . . . . . . . . . . . 64
3.3.7 Opérateurs divers . . . . . . . . . . . . . . . . . . . . . 65
3.4 Evaluation des expressions . . . . . . . . . . . . . . . . . . . . 66
3.4.1 Ordre d’évaluation : précédence et associativité . . . . 66
3.4.2 Conversions . . . . . . . . . . . . . . . . . . . . . . . . . 68
3.4.3 Mélange de types dans les expressions . . . . . . . . . 70
3.5 Instructions et flot de contrôle . . . . . . . . . . . . . . . . . . . 71
3.5.1 Instruction simple et bloc . . . . . . . . . . . . . . . . . 71
3.5.2 Instructions de sélection . . . . . . . . . . . . . . . . . . 72
3.5.3 Instructions de boucle . . . . . . . . . . . . . . . . . . . 75
3.5.4 Instructions de rupture de séquence . . . . . . . . . . . 77
3.6 Exercices du chapitre 3 . . . . . . . . . . . . . . . . . . . . . . . 79
c Jean-Paul R IGAULT
V 2.1 – 7 février 2010
TABLE DES MATIÈRES 5
5 Pointeurs 103
5.1 Déclarations et opérations sur pointeurs . . . . . . . . . . . . . 103
5.1.1 Pointeurs simples . . . . . . . . . . . . . . . . . . . . . . 103
5.1.2 Pointeurs multiples . . . . . . . . . . . . . . . . . . . . . 105
5.1.3 Opérations arithmétiques sur pointeurs . . . . . . . . . 106
5.1.4 Conversions de pointeurs . . . . . . . . . . . . . . . . . 108
5.1.5 Pointeurs et constantes . . . . . . . . . . . . . . . . . . 109
5.2 Pointeurs sur structures et unions . . . . . . . . . . . . . . . . 110
5.2.1 Opérateur de sélection « flêche » . . . . . . . . . . . . . 110
5.2.2 Types récursifs . . . . . . . . . . . . . . . . . . . . . . . 111
5.3 Pointeurs et tableaux . . . . . . . . . . . . . . . . . . . . . . . . 118
5.3.1 Tableaux de pointeurs ; lecture des déclarations de C . 118
5.3.2 Relations entre tableaux et pointeurs . . . . . . . . . . 119
5.4 Exercices du chapitre 5 . . . . . . . . . . . . . . . . . . . . . . . 122
7 Fonctions 141
7.1 Arguments et valeur de retour . . . . . . . . . . . . . . . . . . 141
7.1.1 Type de retour d’une fonction . . . . . . . . . . . . . . 141
7.1.2 Type des arguments d’une fonction . . . . . . . . . . . 142
7.1.3 Mode de passage des arguments . . . . . . . . . . . . . 142
7.2 Déclaration, définition et appel de fonction . . . . . . . . . . . 143
7.2.1 Déclaration du type d’une fonction : prototype . . . . 143
7.2.2 Définition d’une fonction . . . . . . . . . . . . . . . . . 144
c Jean-Paul R IGAULT
V 2.1 – 7 février 2010
TABLE DES MATIÈRES 7
Bibliographie 193
Glossaire 195
Index 203
c Jean-Paul R IGAULT
V 2.1 – 7 février 2010
Table des figures
c Jean-Paul R IGAULT
V 2.1 – 7 février 2010
Liste des tableaux
c Jean-Paul R IGAULT
V 2.1 – 7 février 2010
Liste des programmes
c Jean-Paul R IGAULT
V 2.1 – 7 février 2010
Liste des exercices
c Jean-Paul R IGAULT
V 2.1 – 7 février 2010
Chapitre 1
Introduction
1.1 Historique
Le langage C trouve son origine au début des années 1970, lorsque Dennis
Ritchie rejoint Ken Thomson aux Bell Laboratories. Ce dernier a déjà défini
en 1970 le langage B, dérivé de BCPL, pour implémenter de manière portable
le système d’exploitation qu’il vient d’inventer et qu’il a dénommé Unix.
En 1972, C voit le jour et est utilisé pour réécrire complètement le noyau
d’unix (en un été!). Le langage recevra une définition de référence par la publi-
cation du livre de Brian Kernighan et Dennis Ritchie en 1978 [16]. Le langage
décrit dans ce livre, avec quelques extensions ultérieures (affectation et passage
en arguments de structures et d’unions, introduction du type void), constitue
ce qu’il est convenu d’appeler le « C traditionnel ».
Le développement considérable de C, d’abord entrainé par celui d’Unix
puis relativement autonome, rendit nécessaire une normalisation qui fut entre-
prise à partir de 1983 par le comité x3j11 de l’ansi 1 . Les travaux se terminèrent
fin 1988, par une norme ansi reprise par ieee puis par l’iso (norme iso-9899).
La deuxième édition du livre de Kernighan et Ritchie [17] décrit le langage
ainsi normalisé.
La « compatibilité arrière » a été l’un des soucis de l’ansi, et la plupart des
programmes écrits en C traditionnel peuvent être recompilés avec ansi C. Mais,
sans rien changer à la philosophie du langage, ansi C a apporté de nombreuses
améliorations allant à la fois dans le sens de la sécurité de programmation (typage
fort, meilleure rigueur sémantique) et dans celui de la portabilité (par exemple
définition de la bibliothèque d’exécution minimale).
La version C99 est également largement compatible avec iso C90. Malheu-
reusement de nombreux compilateurs C ne la supporte pas encore complète-
ment. Ainsi, parmi les compilateurs majeurs, gcc 3 et 4 supportent l’essentiel de
C99, mais pas la totalité et Microsoft Visual Studio 2005 (C++ version 8) et 2008
(version 9) ne prétendent même pas supporter cette norme — même s’ils en
ont des parties du fait de leur compatibilité C++. Bref, C99 n’est pas encore très
répandu dix ans après sa normalisation ! C’est pourquoi tous les programmes
donnés ici, sauf mention spéciale, sont compatibles avec C90 aussi bien qu’avec
C99.
1.2 Caractéristiques de C
1.2.1 Un langage contemporain de PASCAL
C a été conçu à peu près en même temps que des langages comme Pascal
et il reflète donc l’état de l’art de l’époque. C’est donc un langage qui supporte
la programmation structurée (on dit aussi procédurale). Il dispose en particulier de
types structurés définissables par l’utilisateur, analogues aux records de Pas-
cal.
c Jean-Paul R IGAULT
V 2.1 – 7 février 2010
1.3. Plan 19
1.3 Plan
Le chapitre 2 permet, à travers une série d’exemples simples, de faire un
tour rapide du langage C.
Les chapitres 3, 4 et 5 reprennent en détail respectivement la description
du langage de base (types, opérateurs et expressions, instructions de contrôle),
celle des agrégats (tableaux, structures et unions) et enfin celle des pointeurs.
Les chapitres 7 et 8 décrivent en détail la notion de fonction et la structure
des programmes. Y est également abordée la programmation modulaire en C.
Le préprocesseur ansi C est étudié en détail au chapitre 6.
L’environnement d’exécution avec une description succinte des biblio-
thèques standards est l’objet du chapitre 9, et l’environnement de développe-
ment celui du chapitre 10.
Enfin le chapitre 11 résume les extensions introduites par C99 et qui n’ont
pas été traitées dans le reste du texte. Quant au chapitre 12 il synthétise les
différences entre C « traditionnel », C90, C99 et C++.
Les programmes complets (ceux qui figurent dans la Liste des programmes,
page 13) sont affichés par le paquettage LATEX listings. Ils sont donc décorés syn-
taxiquement pour mettre en évidence les mots-clés, chaînes de caractères litté-
rales, constantes, etc. En outre, les lignes sont numérotées. Le programme 2.1 du
chapitre suivant en fournit un exemple. On pourra s’étonner de ne pas trouver
de caractères accentués dans ces programmes. Cela est dû au fait que j’utilise le
codage des caractères Unicode (utf-8 pour être précis) qui n’est correctement
supporté ni par le paquettage listings de LATEX ni par les compilateurs C90. En
revanche, nous verrons en 11.3.5 qu’un certain support est prévu en C99.
Lorsque l’on présente un exemple d’interaction entre l’utilisateur et le sys-
tème, on suppose toujours que cette interaction a lieu sous un des shell d’Unix
(ou de Linux, de Cygwin. . .). La syntaxe suppose le shell du à Steve Bourne
sh ou l’un de ses descendants comme bash, ksh, ou zsh. . . (personnellement
j’utilise zsh). L’interaction est présentée comme suit :
% ls -F Stack
CVS/ Makefile Stack* Stack.c Stack.h Stack_main.c
%
Ici % représente la « chaîne de sollicitation » (le prompt) envoyé par le shell, le
texte en oblique gras (comme ls -F Stack) est celui entré par l’utilisateur,
et le texte droit (comme CVS/) celui émis par le système. Les caractères de
contrôle comme ctrl-d (simulation de fin de fichier au terminal sous Unix)
sont représentés sous la forme usuelle utilisant le préfixe ^ (ˆD).
Par ailleurs lorsque ces exemples utilisent des accents, ils supposent un sys-
tème Unix ou Linux correctement configuré. Ainsi la variable d’environnment
LANG doit préciser un encodage 8 bits comme iso88591, iso88591@euro, ou
utf8. Par exemple, la valeur de ma propre variable LANG est en_US.UTF-8
(je n’aime pas avoir les messages du système en français !).
c Jean-Paul R IGAULT
V 2.1 – 7 février 2010
1.6. Ressources : ouvrages et sites Web sur C 21
c Jean-Paul R IGAULT
V 2.1 – 7 février 2010
1.7. Exercices du chapitre 1 23
Si vous êtes sous MS Windows et que vous décidez d’utilisez Microsoft Visual
Studio, sachez que vous disposerez d’un excellent ide à condition de savoir
l’utiliser. L’aide en ligne sur les bibliothèques C est également de haute qua-
lité 3 . Malheureusement, avec Visual Studio, vous n’aurez pas un compilateur
réellement compatible avec la norme C99. Par ailleurs il vous faudra au moins
la version 2005.
Une autre possibilité sous MS Windows est d’apprendre à utiliser l’un des
multiples ide disponibles et qui sont souvent connectés à un compilateurs gcc:
voir 10.
Exercice 1.2 (Accès à la documentation) Elle doit se faire en ligne. Tout sys-
tème de type Unix dispose de la célèbre commande man qui permet d’accéder
à la descriptions des commandes à shell, des fonctions de bibliothèque, des
fichiers système, etc. Apprenez à l’utiliser correctement (faites donc la com-
mande man man), elle ou l’une de ses interfaces comme info ou autre. Notez
que les fonctions des bibliothèques standards de C se trouvent dans la section
2 ou 3 du manuel.
Vous pouvez également recourir à un site Web fournissant une description dé-
taillée de la bibliothèque standard. Il y en a un grand nombre que Google se
fera un plaisir de vous indiquer. Un bon point de départ, comme souvent, est
Wikipédia 4 .
3. http://msdn2.microsoft.com/en-us/library/
4. http://en.wikipedia.org/wiki/C_standard_library. C’est la version en an-
glais, beaucoup plus complète que celle en français.
c Jean-Paul R IGAULT
V 2.1 – 7 février 2010
Chapitre 2
Premiers pas
#include <stdio.h>
5 int main()
{
printf("hello, world!\n");
}
#include <stdio.h>
5 int main()
{
printf("Monde, salut !\n");
}
c Jean-Paul R IGAULT
V 2.1 – 7 février 2010
2.2. Boucle while ; entrées-sorties simples 27
Supposons que ce programme ait été saisi, à l’aide d’un éditeur de texte
quelconque, dans le fichier hello.c. Il ne reste plus qu’à le compiler, par
exemple en utilisant la commande make (que nous considérons comme la seule
commande de compilation sous Unix 2 ) :
% make hello
gcc -g -std=c99 -Wall -o hello hello.c
%
puis à exécuter le fichier binaire produit (qui a été nommé hello par l’option
-o de la commande précédente) :
% hello
hello, world!
%
#include <stdio.h>
5 int main()
{
int c;
Une telle boucle exécute son corps, constitué par une instruction simple ou un
bloc, tant que la condition, qui est une expression entière quelconque, a une
valeur non nulle. La condition est évaluée et testée avant chaque itération, y
compris la première. La boucle while est une des trois formes de boucles dont
dispose C (avec for et do ... while — voir 3.5.3).
Ici la condition
(c = getchar()) != EOF
est une expression relationnelle car != est l’opérateur d’inégalité. Dans a != b
les deux expressions a et b sont évaluées et comparées : si elle sont différentes,
la valeur de l’expression a != b est 1, sinon c’est 0 3 . C possède aussi l’opéra-
teur d’égalité == (attention ==, deux signes = collés !), ainsi que les opérateurs
de comparaison (<, <=, >, >=).
L’opérande droit de != est la valeur prédéfinie EOF (voir ci-après). L’opé-
rande gauche
c = getchar()
est une expression d’affectation, = étant l’opérateur d’affectation. La valeur de
l’expression de droite devient la nouvelle valeur de l’objet référencé par l’ex-
pression de gauche (ici la variable c). La valeur de l’expression d’affectation
elle-même est la nouvelle valeur de l’objet en partie gauche. Donc
(c = getchar()) != EOF
invoque la fonction getchar, copie sa valeur de retour dans la variable c, et
compare cette nouvelle valeur de c à la constante EOF. La boucle while sera
exécutée tant que cette comparaison donnera une valeur non nulle (c’est-à-dire
tant que la nouvelle valeur de c n’est pas EOF). Noter que les parenthèses sont
indispensables pour grouper l’affectation, sinon
c = getchar() != EOF
s’interprète comme
c = (getchar() != EOF)
à cause de la précédence des opérateurs (voir 3.4.1).
Une expression comme
(c = getchar()) != EOF
illustre bien ce type de programmation avec « effet de bord » puisque cette
expression non seulement compare c à EOF, mais encore le fait après avoir
modifié c.
En éliminant l’effet de bord, on aurait pu écrire la boucle while sous une
forme plus habituelle pour un programmeur Pascal, mais certainement moins
« idiomatique » pour un programmeur C :
c = getchar();
while (c != EOF)
{
putchar(c);
c = getchar();
}
3. Contrairement à C99 et C++, C90 ne possède pas de type de donnée booléen mais que ce
sont les valeurs entières 0 et 1 qui sont utilisées pour jouer les rôles respectifs de faux et vrai —
voir 3.3.2 et 11.3.2.
c Jean-Paul R IGAULT
V 2.1 – 7 février 2010
2.3. Fonctions ; expressions ; entrées-sorties 29
% make copy
gcc -g -std=c99 -Wall -o copy copy.c
% copy > fic
ceci est le texte qui sera
recopié sur la sortie standard
ˆD
% cat fic
ceci est le texte qui sera
recopié sur la sortie standard
% copy < fic > fic1
% cat fic1
ceci est un texte qui sera
recopié sur la sortie standard
% cmp fic fic1
%
On rappelle que le caractère ctrl-d (eot) représenté par ˆD simule une fin
de fichier au terminal. Par ailleurs, la commande cmp compare 2 fichiers :
l’absence d’affichage indique que les deux fichiers sont identiques à l’octet près.
1 t
un = u n −1 +
2 u n −1
#include <stdio.h>
#include <stdlib.h>
5 #include <math.h>
double square_root(double);
int main()
10 {
double x;
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
Nous avons déjà remarqué que l’inclusion de stdio.h permet d’impor-
ter les prototypes des fonctions d’entrée-sortie de la bibliothèque stan-
dard ; celle de stdlib.h concerne une bonne partie du reste des pro-
totypes de cette bibliothèque (par exemple celui de la fonction exit) ;
quant à celle de math.h, elle importe les fonctions de la bibliothèque
mathématique comme fabs.
– Un prototype déclarant la fonction square_root, car celle-ci est utilisée
avant d’être définie (on parle de « déclaration en avant » (forward declara-
tion) :
c Jean-Paul R IGAULT
V 2.1 – 7 février 2010
2.3. Fonctions ; expressions ; entrées-sorties 31
double square_root(double);
Cette fonction a un argument qui est un réel double précision (type
double) et retourne une valeur de même type.
– La définition de la constante globale EPS, également de type double, ini-
tialisée à 10−6 .
– La définition des deux fonctions main et square_root.
On note que le format est libre et que l’indentation et le changement de
ligne sont largement utilisés pour rendre le programme lisible (ils n’ont pas
d’autre signification pour le compilateur que celle de séparateurs).
La fonction main utilise une variable locale x, de type double. La valeur
de x est lue grâce à la fonction de bibliothèque scanf. Le premier argument
de scanf est une chaîne de caractères qui spécifie le format de lecture : ici, la
spécification %lg signifie qu’on cherche à lire un réel dans toute présentation
raisonnable (le g), et que ce réel est double précision (le l, comme « long »).
Le deuxième argument de scanf est l’adresse de la variable que l’on cherche
à lire, ici celle de x. En effet l’opérateur & sert à prendre l’adresse d’un objet
(il retourne un pointeur). De manière générale, les arguments de scanf doivent
être des pointeurs 5 . L’avant-dernière instruction de main affiche le résultat de
l’invocation de la fonction square_root avec l’argument x. Là encore le pre-
mier argument de printf est un format où la spécification %g indique que
l’on veut afficher un réel double précision dans un format raisonnable pour
sa valeur. On constate au passage que printf et scanf sont des fonctions à
nombre variable d’arguments.
Bien entendu, l’invocation de exit termine l’exécution du programme,
en renvoyant au shell la valeur de son argument comme code de retour. Par
convention, la valeur 0 indique que tout s’est bien passé 6 . Il n’est pas né-
cessaire d’appeler systématiquement exit à la fin de main, mais ce n’est
pas une mauvaise pratique puisque cela permet de maîtriser le code de re-
tour transmis au shell. Une autre possibilité équivalente est de remplacer
exit(code) par return code (par exemple ici, return 0) — mais évi-
demment ceci n’est équivalent à exit que pour le retour de main, pas d’une
autre fonction ! Notez aussi que le fichier d’entête <stdio.h> définit les deux
constantes entières EXIT_SUCCESS (avec la valeur 0) et EXIT_FAILURE (1) ce
qui permet de remplacer les utilisations de exit ou return précédentes par
exit(EXIT_SUCCESS) ou return EXIT_SUCCESS.
Après la fonction main est définie une constante globale EPS initialisée à la
valeur 10−6 . Enfin, on a déjà remarqué que les commentaires se placent entre
/* et */.
if (condition)
instruction-si-vrai
ou encore celle d’une alternative
if (condition)
instruction-si-vrai
else
instruction-si-faux
Sa signification est évidente. Les deux instructions sont soit des instructions
simples soit des blocs 7 .
La fonction fprintf est analogue à printf, mais son premier argument
précise le flux de sortie, ici l’erreur standard stderr 8 .
Le cœur de square_root est un boucle while où current est la valeur
courante de un et previous la valeur précédente (i.e., un−1 ). La fonction de
bibliothèque fabs calcule la valeur absolue réelle double précision (son proto-
type est dans math.h). La boucle s’exécute tant que l’écart en valeur absolue
entre un et un−1 est supérieur à EPS. Le corps de la boucle étant constitué de
deux instructions simples (deux instructions d’affectation en fait), il faut donc
là encore les englober dans un bloc. L’expression arithmétique
0.5 * (current + t / current)
se comprend sans problème. C dispose entr’autres, des opérateurs arithméti-
ques habituels : addition (+), soustraction (-), multiplication (*), division (/).
Ces opérateurs s’appliquent à des valeurs entières ou réelles : attention, si ses
deux opérandes sont entiers, / est la division entière.
A la fin de la boucle, la valeur de current est le résultat cherché et est donc
transmise à l’appelant grâce à l’instruction return qui termine la fonction.
c Jean-Paul R IGAULT
V 2.1 – 7 février 2010
2.3. Fonctions ; expressions ; entrées-sorties 33
Conversions en sortie
Dans le cas de printf (ou de fprintf), tout ce qui n’est pas une spécifica-
tion de conversion est imprimé tel quel. Ainsi
printf("hello, world!\n");
est un exemple où il n’y a aucune spécification de conversion dans le format.
Dans
int i = 3;
int j = 4;
...
printf("i = %d, j = %d\n", i, j);
les valeurs de i et j sont imprimées en format décimal (%d) produisant
i = 3, j = 4
suivi d’une fin de ligne.
Il existe un grand nombre de possibilités de spécifications de conversion;
voici quelques unes des plus fréquentes :
%d conversion d’un entier en décimal
%o conversion d’un entier en octal
%x conversion d’un entier en hexadécimal
%% le caractère % lui-même
Le format, comme toute chaîne, peut contenir des « séquences d’échappe-
ment » qui représentent certains caractères non imprimables. Ces séquences se
composent toutes du caractère \ suivi d’un seul caractère. Nous avons déjà
rencontré \n, voici les plus fréquentes :
9. La discussion qui suit s’applique aussi au deuxième argument de fprintf ou de fscanf
dont le premier argument désigne le flux d’entrée-sortie.
\n fin de ligne
\t tabulation horizontale (tab)
\f saut de page (form feed)
\0 le caractère « nul » (nul)
Le caractère nul joue un rôle fondamental comme terminateur de chaîne
de caractères (voir 3.1.7). Ne pas confondre ce caractère nul avec la constante
NULL désignant le pointeur nul (voir 5.1.1).
Conversions en entrée
Nous avons déjà vu que les arguments de scanf sont des pointeurs. Les
formats et les spécifications de conversion ont pratiquement la même syntaxe
que pour printf, tout au moins en première approximation. Le texte autre
que les spécifications de conversion et les espaces doit être présent dans l’en-
trée effective. Les espaces eux ne servent que de séparateurs et pour le reste
sont ignorés (le blanc, \n, \t, \f sont des espaces). L’exécution de scanf se
termine lorsque toutes les spécifications de conversion ont été satisfaites ; elle
est abandonnée prématurément dès qu’une conversion ne peut être effectuée.
Dans tous les cas, la valeur de retour est le nombre de conversions effectivement
réalisées.
#include <stdio.h>
5 int main()
{
int i;
double x;
int n;
10
n = scanf("%d toto %lg", &i, &x);
printf("n = %d, i = %d, x = %lg\n", n, i, x);
}
c Jean-Paul R IGAULT
V 2.1 – 7 février 2010
2.4. Tableaux et instruction d’itération 35
12 toto
3
n = 2, i = 12, x = 3
% scanf
12toto3
n = 2, i = 12, x = 3
% scanf
12toto
3
n = 2, i = 12, x = 3
% scanf
12 3
n = 1, i = 12, x = 1.9064e-310
% scanf
12 to
n = 1, i = 12, x = 1.9064e-310
% scanf
aaaaaa 12 toto 3
n = 0, i = -1081747800, x = 1.9064e-310
%
Les valeurs étranges de x et i (en rouge) sont bien évidemment le symp-
tome que ces variables n’ont pas été initialisées (d’ailleurs, on remarque que n
est inférieur à 2 dans ces cas).
Les exemples précédents montrent, s’il en est besoin, que scanf est une
fonction extrêmement délicate à manipuler! À l’avenir, nous l’éviterons le plus
possible!
#include <stdio.h>
#include <stdlib.h>
5
void insertion_sort(int tab[], int n)
{
int i, j, current;
10 if (n <= 1)
return;
for (i = 1; i < n; i++)
{
current = tab[i];
15 for (j = i - 1; j >= 0; j--)
{
if (tab[j] <= current)
break;
else
20 tab[j + 1] = tab[j];
}
tab[j + 1] = current;
}
}
25
#define NMAX 1000
int main()
{
30 int n, i;
int t[NMAX];
c Jean-Paul R IGAULT
V 2.1 – 7 février 2010
2.4. Tableaux et instruction d’itération 37
être suivie d’une expression dont l’évaluation fournit la valeur retournée par
la fonction. Mais ici, la fonction square_root a pour type de retour void ce
qui signifie qu’elle ne retourne aucune valeur (plutôt qu’une fonction, c’est une
procédure).
La boucle for permet de parcourir le tableau tab, passé en argument, dont
la dimension (le nombre de composantes) est n. Malgré sa syntaxe étrange, la
boucle for est assez simple à comprendre. En fait
for (initialisation; entretien; rebouclage)
corps
est (à peu près) équivalent à
initialisation;
while (entretien)
{
corps
rebouclage
}
Donc dans notre cas
for (i = 1; i < n; i++)
{
corps
}
est très précisément équivalent à
i = 1;
while (i < n)
{
corps
i++;
}
L’instruction i++ est l’incrémentation de i, ici équivalente à i = i + 1. Cette
boucle for exécute son corps pour les valeurs de i variant de 1 à n-1 inclus.
C possède aussi l’opérateur ou logique représenté par ||. Ces deux opéra-
teurs (|| et &&) sont évalués de gauche à droite, et l’évaluation s’interrompt
dès que le résultat est déterminé.
La fonction main définit un tableau local t de dimension maximale NMAX.
Ce tableau est initialisé composante par composante grâce à scanf. On re-
marque que le corps de la boucle est vide, tout le travail ayant été fait par
effet de bord dans la condition d’entretien. A la fin de la boucle, n est le
nombre d’éléments lus. La liste triée par invocation de insertion_sort
est imprimée élément par élément grâce à printf. On n’oublie pas d’ajou-
ter une fin de ligne à la fin de la liste. Noter la différence entre les chaînes
de caractères littérales (entre ") et les caractères littéraux (entre ’).
Voici un exemple d’exécution :
% make insertion_sort
gcc -g -std=c99 -Wall -o insertion_sort insertion_sort.c
% insertion_sort
entrez une liste d’entiers terminée par EOF? 12 -3
2 -1 7 -13 18 12 -27
ˆD
liste triee = -27 -13 -3 -1 2 7 12 12 18
%
u n = u n −1 + u n −2
u =0
0
u1 = 1
c Jean-Paul R IGAULT
V 2.1 – 7 février 2010
2.5. Arguments du shell ; fonction récursive 39
#include <stdio.h>
#include <stdlib.h>
5
int fibo(int n)
{
if (n < 2)
return n;
10 else
return fibo(n-1) + fibo(n-2);
}
if (argc == 2)
{
20 i = atoi(argv[1]);
}
else
{
fprintf(stderr, "usage: fibo n\n");
25 exit(1);
}
printf("fibo(%d) = %d\n", i, fibo(i));
exit(0);
}
if (n < 2)
r = n;
else
r = fibo(n-1) + fibo(n-2);
c Jean-Paul R IGAULT
V 2.1 – 7 février 2010
2.6. Exercices du chapitre 2 41
On peut représenter ce calcul récursif sous forme d’un arbre comme dans
la figure 2.2. La fonction fibo évalue cet arbre de la gauche vers la droite,
« en profondeur d’abord », c’est à-dire en évaluant effectivement les « feuilles »
(indiquées en bleu) d’abord.
Exercice 2.1 (Mise en route) Utiliser votre éditeur favori pour saisir le texte de
l’un des exemples de ce chapitre. Compiler et exécuter cet exemple. Noter la co-
loration syntaxique (syntax highlighting) utilisée par votre éditeur (la plupart des
éditeurs actuels en disposent). Si votre éditeur le permet ou si vous utilisez un
environnement intégré comme kdevelop, eclipse, visual studio ou dev-c++,
habituez-vous à compiler et à corriger vos erreurs sans quitter cet éditeur ou cet
environnement : cela fait gagner beaucoup de temps et évite les manipulations
stupides 12 !
Exercice 2.2 (Copie de fichiers) Modifier copy (programme 2.3) pour qu’il im-
prime le nombre de lignes copiées sur l’erreur standard (stderr).
12. comme par exemple modifier un fichier et le compiler sans l’avoir sauvegardé au préa-
lable. . .
Exercice 2.3 (La commande iota) Écrire le programme iota qui, lorsqu’il est
invoqué depuis le shell avec l’argument n produit la suite des entiers de 1 à n ;
par exemple
% iota 7
1 2 3 4 5 6 7
%
9
F= C + 32
5
Exercice 2.8 (Mauvais format) Écrire un programme qui utilise printf pour
imprimer un réel (float ou double) en format %d, un entier (int) en format
%f et un caractère (char) en format %d.
Faites le même exercice mais en lecture, avec scanf. Que concluez-vous ?
Remarque
Pour cet exercice, ignorez les messages d’avertissement (warnings) éventuels du
compilateur. Pouvez-vous imaginer un moyen de faire taire ces messages ?
c Jean-Paul R IGAULT
V 2.1 – 7 février 2010
Chapitre 3
Bases du langage
0 1 2 3 4 5 6 7
0 nul dle sp 0 @ P ‘ p
1 soh dc1 ! 1 A Q a q
2 stx dc2 " 2 B R b r
3 etx dc3 # 3 C S c s
4 eot dc4 $ 4 D T d t
5 enq nak % 5 E U e u
6 ack syn & 6 F V f v
7 bel etb ’ 7 G W g w
8 bs can ( 8 H X h x
9 tab em ) 9 I Y i y
A nl sub * : J Z j z
B vt esc + ; K [ k {
C ff fs , < L \ l |
D cr gs - = M ] m }
E so rs . > N ^ n ~
F si us / ? O _ o del
c Jean-Paul R IGAULT
V 2.1 – 7 février 2010
3.1. Eléments lexicaux 45
Les séparateurs permettent d’isoler les autres éléments lexicaux. Les opé-
rateurs sont aussi des séparateurs. Les espaces, c’est-à-dire le blanc (sp), la fin
de ligne (nl), les tabulations horizontale (tab ou ht) ou verticale (vt) et le saut
de page (ff) sont des séparateurs « purs » en ce sens que pour le reste ils sont
ignorés.
3.1.3 Commentaires
En C90 les commentaires sont encadrés entre /* et */ et peuvent s’étendre
sur plusieurs lignes mais ne peuvent pas s’imbriquer :
/* Un long commentaire
sur
3 lignes */
/* Une /* erreur de
syntaxe */ à coup sûr ! */
Ce type de commentaire est équivalent à un espace pour le compilateur (c’est
donc un séparateur).
C99 autorise aussi les commentaires « à la C++ » commençant par // et C99
s’étendant jusqu’à la fin de la ligne. Il est possible d’imbriquer un commentaire
C90 dans un commentaire C99 ou l’inverse :
// un commentaire-ligne
3.1.4 Identificateurs
Les identificateurs permettent de nommer des objets (constantes, variab-
les, fonctions). Ils commencent par une lettre ou le caractère souligné (_) suivi
d’une séquence de lettres, de chiffres ou de soulignés. Par exemple :
x X toto NBUF
j21 a2ps char_0_to_9
Prix_HT Square_Root num_secu PascalStyleId
_strcmp __DATE__ __STDC__ _1993
En revanche, les mots suivants ne sont pas des identificateurs :
#x 2#$!**)((_+%^ 2toto Prix-TTC
On rappelle que les majuscules et les minuscules sont considérées comme
des caractères différents : Square_Root et square_root ne sont pas le même
identificateur.
Portabilité : jeu de caractères étendu pour les identificateurs
En C90, seul le code ascii peut être utilisé pour coder les identificateurs
(noms de constantes, variables, fonctions, macros. . .).
En revanche, C99 permet l’utilisation des caractères étendus (Unicode) C99
3.1.5 Mots-clés
Les mots-clés de C sont strictement réservés. Ils ne doivent pas être utili-
sés comme identificateurs sinon le programme devient incompilable. Tous les
mots-clés sont en minuscules ; ils sont au nombre de 32 pour C90 et le ta-
bleau 3.2 en donne la liste.
c Jean-Paul R IGAULT
V 2.1 – 7 février 2010
3.1. Eléments lexicaux 47
#include <stdio.h>
5 int main()
{
int c;
c Jean-Paul R IGAULT
V 2.1 – 7 février 2010
3.1. Eléments lexicaux 49
En mémoire, une chaîne littérale est représentée par la suite des caractères
qui la constitue (sans les doubles « quotes » bien sûr) terminée par le caractère
nul (\0). Ainsi, une chaîne occupe-t-elle un caractère de plus que sa longueur
« utile » (figure 3.1).
Cette propriété, qui est imposée par le compilateur pour les chaînes littéra-
les, est une convention qui doit être respectée par le programmeur en manipu-
lant des chaînes variables. A titre d’exemple, les deux programmes 3.2 et 3.3
utilisent cette propriété de terminaison des chaînes : le premier pour détermi-
ner la longueur utile, le second pour recopier une chaîne dans une autre —
noter que le caractère nul final est lui aussi recopié.
#include <stdio.h>
int main()
{
15 printf("%d\n", string_length("hello, world\n"));
}
Pour ces deux programmes, on remarque que l’argument orig est passé
sous forme d’un tableau de caractères constants
const char orig[]
ce qui signifie que les éléments du tableau ne sont pas modifiés dans le
corps de la fonction (voir 7.3.2). En revanche, le tableau dest, paramètre de
copy_string, est évidemment modifiable, d’ou l’absence de const. Noter
aussi le corps vide de la boucle de la fonction string_length : tout le travail
est en effet réalisé dans l’entête de l’instruction for.
Attention : ’a’ et "a"
On ne doit donc pas confondre la constante charactère ’a’ avec la chaîne
#include <stdio.h>
int main()
15 {
char str[100];
c Jean-Paul R IGAULT
V 2.1 – 7 février 2010
3.2. Types scalaires et déclarations simples 51
de 32 bits, un int est codé sur 32 bits (ce qui donne un intervalle de va-
leurs de -2 147 483 648 à +2 147 483 647 pour un signed en complément
à 2) et de 0 à 4 294 967 295 pour un unsigned).
4. Les intervalles de valeurs effectivement utilisés par une implémentation
sont définis dans le fichier d’environnement <limits.h>.
c Jean-Paul R IGAULT
V 2.1 – 7 février 2010
3.2. Types scalaires et déclarations simples 53
La syntaxe d’une déclaration de variable d’un type de base scalaire est im-
médiate : elle a la forme
nom-du-type liste-d’identificateurs ;
Par exemple :
int i;
short int i, j, k;
long l1, l2;
Une variable peut être initialisée lors de sa définition :
int i = 1;
unsigned long ul1 = 0xFFFF0000;
float x = 3.141592f;
double y = 2.7182818284;
L’initialisation se fait individuellement pour chaque variable : ainsi, dans
int i, j = 2, k, l = 1;
seuls j et l sont initialisés.
Si une variable n’est pas initialisée lors de sa définition, C utilise des règles
par défaut qui dépendent du contexte de définition (voir 8.3.3) : dans certains
cas la variable n’est pas initialisée du tout. Moralité : si une variable doit être
initialisée, elle doit l’être explicitement.
Une définition peut être précédées de const : l’objet est alors une constante
(symbolique) : sa valeur ne pourra plus être modifiée. Une conséquence immé-
diate est qu’une définition de constante comporte nécessairement son initiali-
sation.
while (io_register == 0)
{
sleep(1); /* attendre 1 seconde */
}
}
un compilateur astucieux pourrait, en l’absence de la déclaration volatile,
s’apercevoir que io_register n’est pas modifié dans le corps de la boucle,
qu’étant une variable locale, il ne peut être modifié ailleurs, et que l’on peut
donc transformer la fonction en la forme équivalente suivante comportant une
boucle infinie et sans la variable locale puisqu’elle ne sert à rien !
void test_io(void)
{
while (1) /* boucle infinie */
{
}
}
La présence de volatile, en signalant la possibilité que io_register puisse
être modifié à l’extérieur du programme empêche cette optimisation : l’état de
de io_register sera donc bien vérifié toutes les secondes jusqu’à ce qu’il
devienne non nul.
Une constante peut être volatile :
const volatile double vc;
Cela signifie que l’objet vc ne peut être modifié par programme mais peut l’être
par l’extérieur. Dans ce cas, la définition de constante n’exige pas d’initialisa-
tion.
c Jean-Paul R IGAULT
V 2.1 – 7 février 2010
3.2. Types scalaires et déclarations simples 55
En fait, les valeurs que peut prendre un objet de type é numéré sont des
entiers, débutant à 0 par défaut. Dans les exemples précédents, VERT vaut 0 et
ALICE vaut 3. On peut même forcer les valeurs que l’on souhaite pour certains
éléments :
enum instruction
{
LOAD = 0xfa,
STORE, /* 0xfb */
ADD = 0x10,
SUB, /* 0x11 */
MUL, /* 0x12 */
DIV, /* 0x13 */
JUMP = 0x20,
HALT = 0xff,
};
Dans une certaine mesure (voir cependant 3.4.2) on peut mélanger les objets de
types énumérés et les entiers.
Deux types énumérés différents ne peuvent avoir des valeurs de même
nom :
enum color {GREEN, ORANGE, RED};
enum fruit {APPLE, PEAR, ORANGE}; /* erreur! */
3.3 Opérateurs
Les opérateurs combinés avec les identificateurs et les constantes littérales
ou symboliques permettent d’écrire des expressions. Chaque expression a un
type et une valeur, résultat de son évaluation. Le tableau 3.7 donne la liste
de tous les opérateurs du langage avec leur signification et la référence de la
section où ils sont étudiés.
c Jean-Paul R IGAULT
V 2.1 – 7 février 2010
3.3. Opérateurs 57
gcd(12, 7) = 1
% gcd 24 12
gcd(24, 12) = 12
% gcd 27 63
gcd(27, 63) = 9
% gcd -27 63
gcd(-27, 63) = 9
% gcd -27 -63
gcd(-27, -63) = 9
% gcd 0 0
gcd(0, 0) = 0
%
Opérateurs relationnels
Les opérateurs relationnels sont utilisables avec tout type scalaire entier ou
réel. Ils sont au nombre de six : <, >, <=, >=, == et !=. Noter que l’opérateur
d’égalité est == et non pas = — ce dernier est l’opérateur d’affectation (3.3.4).
Remarque sur l’identité entre booléens et entiers
Le fait qu’une expression comme a < b retourne un entier (0 ou 1) permet
d’écrire des expressions « étranges » comme
4*(a < 0) + 8*(a < 4) + 16*(a < 8)
Ce style de programmation est en général douteux.
Portabilité : comparaison des réels
Comme souvent dans les langages de programmation, on doit être très
prudent dans les tests entre réels : le résultat peut dépendre de la précision
du processeur.
c Jean-Paul R IGAULT
V 2.1 – 7 février 2010
3.3. Opérateurs 59
#include <stdio.h>
#include <stdlib.h>
5
int max(int a, int b)
{
return (a > b) ? a : b;
}
10
int min(int a, int b)
{
return (a < b) ? a : b;
}
15
int gcd(int a, int b)
{
int a0 = abs(a); /* abs est defini dans <stdlib.h> */
int b0 = abs(b);
20 int p = max(a0, b0);
int q = min(a0, b0);
x = atoi(argv[1]);
y = atoi(argv[2]);
printf("gcd(%d, %d) = %d\n", x, y, gcd(x, y));
40 return 0;
}
Négation logique
0 si a 6= 0
!a =
1 si a = 0
Complément à 1
ET et OU bit à bit
c Jean-Paul R IGAULT
V 2.1 – 7 février 2010
3.3. Opérateurs 61
Décalages
#include <stdio.h>
5 int main()
{
unsigned int ui = 0x7FFFFFFF; /* 01111...11 */
int i = ui;
int j = -1;
10 int k = 0xBFFFFFFF; /* 10111...11 */
Exemple
#include <stdio.h>
#include <limits.h>
5 #include <assert.h>
int main()
{
35 Bit_Array b = 0x00;
set_bit(b, 156);
b = set_bit(b, 3);
40 if (test_bit(b, 3))
printf("Jusqu’ici, tout va bien\n");
b = reset_bit(b, 3);
if (b == 0)
printf("Cela a l’air de marcher...\n");
45
return 0;
}
c Jean-Paul R IGAULT
V 2.1 – 7 février 2010
3.3. Opérateurs 63
3.3.4 Affectations
Affectation simple
Attention : = et ==
La possibilité de cet effet de bord rend encore plus dangereuse la confusion
entre l’opérateur d’affectation (=) et celui d’égalité (==). Dans
if (a = 0) ...
Affectation composée
avec cette différence que a n’est évalué qu’une seule fois. Cette dernière restric-
tion ne pose pas de problème si a est une simple variable. mais nous verrons
que la situation peut être plus complexe (5.1.1). Noter les parenthèses autour
de b qui forcent l’évaluation de cette expression avant d’appliquer l’opérateur
∇.
Les opérateurs binaires composables avec l’affectation (c’est-à-dire éligibles
pour remplacer ∇) sont les suivants :
* / % + - << >> & ^ |
Voici quelques exemples d’utilisation :
a += 2 incrémentation de a de 2
a *= 3 triplement de a
a %= 2 a remplacé par son « imparité »
a <<= 3 a decalé à gauche de 3 bits
a |= m les bits à 1 de m le seront aussi dans a
a &= ∼m les bits à 1 de m seront à 0 dans a ...
Dans les deux derniers cas, les autres bits de a sont inchangés.
Incrémentation et décrémentation
Pour (tenter de) convertir une expression e dans le type T, il suffit d’écrire
(T)e :
float x = (float)3; /* float x = 3.0; */
...
x = 1/(double)2; /* x = 1/2.0; */
...
c = (char)0; /* c = ’\0’; */
En C, cette conversion n’a de sens que si le type T et celui de a sont scalaires.
La sémantique de ces conversions est étudiée en 3.4.2 et en 5.1.4. En C il s’agit
toujours d’un mécanisme résolu à la compilation.
On trouve le nom d’un type en considérant une déclaration d’un objet du
type et en y retirant l’identificateur correspondant à l’objet déclaré :
c Jean-Paul R IGAULT
V 2.1 – 7 février 2010
3.3. Opérateurs 65
Expression conditionnelle
if (n < 100)
b = t[n];
else
a = 0;
c Jean-Paul R IGAULT
V 2.1 – 7 février 2010
3.4. Evaluation des expressions 67
Associativité
() [] -> . gauche à droite
Opérateurs unaires droite à gauche
! ∼ - + *
& ++ -- (type) sizeof
* / \% gauche à droite
+ - gauche à droite
<< >> gauche à droite
< <= > >= gauche à droite
== != gauche à droite
& gauche à droite
^ gauche à droite
| gauche à droite
&& gauche à droite
|| gauche à droite
? : (conditionnelle) droite à gauche
Affectations simples et composées droite à gauche
= += *= ...
, (virgule) gauche à droite
a < b < c
doit s’interpréter comme
(a < b) < c
ce qui compare le résultat de (a < b) (0 ou 1) à la valeur de c, ce qui n’est
sans doute pas ce qui est désiré.
De manière plus souhaitable, on note que les affectations associent de droite
à gauche, ce qui permet d’écrire
a = b = c
qui est interprété comme
a = (b = c)
et qui affecte à a et b la même valeur, celle de c — au type près (voir 3.4.3).
Certaines priorités sont « piégeantes » : par exemple
if (a == b&0xff) ...
doit se lire
if ((a == b)&0xff) ...
ce qui n’est pas particulièrement naturel. On doit également se méfier de l’ex-
pression conditionnelle :
x = 3 + a ? b : c;
s’interprête comme
x = (3 + a) ? b : c;
3.4.2 Conversions
ansi C permet d’effectuer des conversions entre types scalaires, soit expli-
citement (comme en 3.3.6), soit implicitement comme on va le voir un peu plus
loin (3.4.3). Cette section donne les règles qui sont alors appliqueés, c’est-à-dire
la sémantique des conversions.
Une règle générale simple est utilisée pour toutes ces conversions : si la
valeur initiale peut être représentée dans le nouveau type, la valeur est conser-
vée 7 ; sinon le résultat dépend de l’implémentation.
Promotion entière
c Jean-Paul R IGAULT
V 2.1 – 7 février 2010
3.4. Evaluation des expressions 69
Lorsqu’un entier signé a est converti en non signé, la valeur obtenue u est
telle que
u = min(v ≡ a (mod 2n ))
v >0
c Jean-Paul R IGAULT
V 2.1 – 7 février 2010
3.5. Instructions et flot de contrôle 71
if {a != b)
{
x = a;
y = b;
}
Les instructions if s’enchainent très bien grâce à la forme else if :
if (isalpha(c))
printf("lettre\n");
else if (isdigit(c))
printf("chiffre\n");
else if (isspace(c))
printf("espace\n");
else
printf("%c ni lettre, ni chiffre, ni espace\n");
Bien entendu le dernier else est optionnel. Les fonctions isalpha, isdigit
et isspace permettent de déterminer à quelle catégorie appartient un carac-
tère ; elles sont définies dans le fichier d’environnement standard <ctype.h>.
c Jean-Paul R IGAULT
V 2.1 – 7 février 2010
3.5. Instructions et flot de contrôle 73
c Jean-Paul R IGAULT
V 2.1 – 7 février 2010
3.5. Instructions et flot de contrôle 75
Boucle while
Boucle for
c Jean-Paul R IGAULT
V 2.1 – 7 février 2010
3.5. Instructions et flot de contrôle 77
Ici un élément sur deux du tableau u est transféré dans les dix premiers élé-
ments (consécutifs) du tableau t.
Attention : boucles simples et imbriquées
Dans l’exemple précédent, il n’y a qu’une seule boucle et les deux indices
i et j progressent en même temps, bien qu’avec des pas différents. Le
nombre d’itérations est donc de 10. Il ne faut pas confondre cet exemple
avec celui des boucles for imbriquées comme
for (i = 0; i < 10; i++)
for (j = 0; j < 20; j += 2)
...
L’instruction break que nous avons déjà rencontré dans le cas de switch
(3.5.2) a une autre signification : elle permet d’abandonner prématurément une
boucle (for, while ou do ... while). Ainsi
for (i = 0; i < 10; i ++)
{
if (t[i] == k)
break;
}
est une boucle qui se termine lorsque t[i] vaut k. Noter qu’en sortie de boucle,
la valeur de i est conservée.
Remarque
L’exemple précédent peut aussi s’écrire
c Jean-Paul R IGAULT
V 2.1 – 7 février 2010
3.6. Exercices du chapitre 3 79
...
La_Bas: /* La_Bas, c’est ici */
...
Une étiquette est un identificateur quelconque. Par exemple, pour sortir à la
fois d’un switch et de la boucle qui l’englobe :
for (i = 0; i < 10; i++)
{
switch (t[i])
{
case 0:
...
case 1:
...
goto out;
...
}
}
out:
...
On peut faire un goto uniquement vers une étiquette définie dans le même
bloc ou dans un bloc englobant. Il est interdit de faire des goto de l’extérieur
vers l’intérieur d’un bloc, du corps d’une boucle, d’un if ou d’un switch.
Enfin signalons que l’instruction goto est considérée comme nuisible de-
puis un article célèbre de E. W. Dijsktra [13]. En tout état de cause, son utili-
sation est très souvent l’indice d’une mauvaise organisation du code.
Nous avons déjà souvent utilisé cette instruction qui permet de terminer
l’exécution d’une fonction. Elle a deux formes possibles :
return;
si la fonction ne rend pas de résultat (son type de retour est void) ou
return expression;
si la fonction à une valeur de retour. L’expression sera convertie dans le type
de retour, si nécessaire et possible — sinon, c’est une erreur.
Parenthésage de l’expression suivant return
Comme return n’est pas une fonction (contrairement, par exemple, à
exit()), il n’y à pas besoin de parenthèses autour de l’expression à re-
tourner.
Exercice 3.1 (Nombre de bits à 1 dans un octet) Écrire un programme qui im-
prime combien de bits sont à 1 dans un octet.
Exercice 3.2 (Nombre de caractères, mots et lignes d’un texte) Écrire le pro-
gramme word_count qui lit son entrée standard et imprime sur la sortie stan-
dard le nombre de lignes, mots et caractères lus sur l’entrée standard. Votre
programme doit avoir le même comportement que la commande d’Unix wc(1)
mais, évidemment, ne doit pas utiliser cette dernière !
On pourra utiliser les fonctions définies dans <ctype.h> en particulier
isspace pour tester qu’un caractère est un espace.
Remarque 1
On précisera ce qu’on entend par un mot et donc quels sont les séparateurs de
mots. Ce doit être la même chose que pour la commande standard wc(1).
Remarque 2
Attention à la fin de fichier !
Remarque 3
On pourra ajouter au programme des options -l, -c... comme dans wc(1).
(. . . (( an x + an−1 ) x + an−2 ) x + . . . + a1 ) x + a0
et peut donc être évalué par une procédure récursive ou itérative (que nous
vous laissons le soin de définir et de mettre en place) ne nécessitant que n
multiplications.
Voici un exemple d’utilisation :
% poly 2 3 4
polynome: 2 + 3 * xˆ1 + 4 * xˆ2
valeur de x ? 2
résultat direct: 24
résultat Horner: 24
%
Comme vous pouvez le constater, on a aussi écrit une fonction qui permet
d’afficher le polynôme sous une forme raisonnable.
Exercice 3.4 (Nombres premiers) Écrire un programme qui reçoit une suite
d’entiers en ligne de commande et pour chacun de ces entiers imprime s’il
est premier ou non.
c Jean-Paul R IGAULT
V 2.1 – 7 février 2010
3.6. Exercices du chapitre 3 81
c Jean-Paul R IGAULT
V 2.1 – 7 février 2010
Chapitre 4
4.1 Tableaux
4.1.1 Tableaux mono-dimensionnels
Définition et déclaration
Un tableau est bien sûr une collection d’objets qui sont tous du même type.
Nous avons déjà rencontré des déclarations et définitions de tableaux mono-
dimensionnels (2.4). En voici quelques exemples :
int t[10];
double vec[100];
...
#define NBUF 10000
char buffer[NBUF];
La dimension, c’est-à-dire le nombre d’éléments, doit être une expression sta-
tique : la taille des tableaux de C90 doit donc être connue à la compilation (voir
4.1.4 pour C99). Il y a cependant exception lorsqu’un tableau est passé en argu-
ment d’une fonction 1 : la dimension du tableau peut être laissée libre, à condi-
tion que la fonction puisse la connaitre par ailleurs. La dimension d’un tableau
de C n’est en effet pas conservée à l’exécution : en particulier, C ne sait pas vé-
rifier les débordements d’indice 2 . A titre d’exemple, dans le programme 4.1, la
fonction prod_scal calcule le produit scalaire des deux vecteurs de dimension
n donnés en arguments.
Remarque : dimension d’un tableau et const
En C90, on ne peut malheureusement pas fixer la dimension d’un tableau
à l’aide d’une const :
const int N = 100;
double t[N]; /* ERREUR!! */
1. Nous verrons plus tard (5.3.2) que ce n’est pas vraiment le tableau qui est passé en para-
mètre mais plutôt un pointeur, l’adresse de son premier élément.
2. Certains compilateurs, ou certains outils de l’environnement de programmation, peuvent
cependant proposer un tel service, mais il n’est pas défini dans la norme.
#include <stdio.h>
#include <stdlib.h>
5
double prod_scal(int n, double v1[], double v2[])
{
int i;
double p;
10
for (i = 0; i < n; i++)
p += v1[i]*v2[i];
return p;
}
15
int main()
{
double x1[5] = {0, 1, 2, 3, 4};
double x2[5] = {3, 1, -1, -1, 1};
20 printf("<x1, x2> = %g\n", prod_scal(5, x1, x2));
return 0;
}
mais ceci est loin d’avoir les mêmes avantages (voir 6.3.1).
C99 En revanche, C99 permet ce type de dimensionnement par une constante,
mais uniquement pour les variables locales (voir 4.1.4).
On peut bien sûr définir un synonyme pour le nom d’un type tableau ; la
dimension fait partie du type :
typedef double Vector[1000];
Vector v1, v2;
Ici Vector est le type « tableau de 1000 double ».
c Jean-Paul R IGAULT
V 2.1 – 7 février 2010
4.1. Tableaux 85
Bien entendu, un tableau peut être constant (resp. volatile), ce qui revient à
dire que toutes ses composantes le sont :
const int ctab[] = {1, 0, 0, 0};
const volatile unsigned status_registers[NDEVICES];
Dans ce cas, ctab[i] ne peut être modifié et status_registers[i] peut
changer de valeur sans que le programme ne le modifie explicitement.
Opération d’indexation
Copie de tableaux
Comme nous le justifierons plus tard (5.3.2), on ne peut pas affecter les
tableaux entre eux. Cet extrait de programme est incompilable :
int t[10], u[10];
...
u = t; /* ERREUR */
Pour faire la copie il faut utiliser une boucle
for (int i = 0; i < 10; ++i)
u[i] = t[i];
ou alors utiliser une fonction système telle que memcpy :
memcpy(u, t, 10*sizeof(int));
c Jean-Paul R IGAULT
V 2.1 – 7 février 2010
4.1. Tableaux 87
#include <stdio.h>
5 #define N 3
typedef double Mat3x3[N][N];
int main()
{
int i, j;
25 Mat3x3 mat;
Transpose(N, mat);
35 printf("Resultat:\n");
for (i = 0; i < N; i++)
{
for (j = 0; j < N; j++) printf("%g ", mat[i][j]);
putchar(’\n’);
40 }
}
c Jean-Paul R IGAULT
V 2.1 – 7 février 2010
4.1. Tableaux 89
% c99_var_array
n? 10
main: n = 10 sizeof(tab) = 40
f: n = 10 sizeof(t) = 4 sizeof(u) = 4000
%
#include <stdio.h>
int tab[n];
printf("main: n = %d sizeof(tab) = %d\n", n, sizeof(tab));
20 f(n, tab);
}
car tab n’est pas locale. Le fait que n soit constante ou pas ne change rien
à l’affaire.
Notons que mécanisme permet au passage de dimensionner un tableau
local par une constante : en fait il permet déjà de le faire par une variable
et donc, qui peut le plus, peut le moins.
4.2 Structures
La structure C est l’analogue du record des langages de la famille de Pas-
cal. Elle permet de regrouper en un seul type « composite » une collection
d’objets dont les types sont possiblement différents.
3. une déclaration, pas une définition : en particulier, il ne peut pas y avoir d’initialisation.
4. dont 99 utiles
5. dont 49 utiles
c Jean-Paul R IGAULT
V 2.1 – 7 février 2010
4.2. Structures 91
et dans les deux cas, le nom du type sera Address tout court :
Address addr_epunsa = {
930, "route des Colles",
6903, "Sophia Antipolis",
};
Cependant, dans ce dernier cas, le type étant anonyme, on ne pourra pas créer
d’autres objets de ce type structure.
Combiner dans la même instruction à la fois la définition d’un type et la
définition d’objets de ce type est de style douteux, et devrait être évité.
Remarque : initialisation d’agrégat
En C90, dans la définition de l’objet tel que jpr ci-dessus, en supposant
l’objet addr_epunsa préalablement initialisé, il est impossible d’écrire
simplement (quel que soit le contexte)
struct Person jpr = {
"Jean-Paul", "Rigault",
FACULTY,
addr_epunsa,
};
int i = random();
struct {
int a;
double x;
} s = {i, i * 5};
// ...
}
Dans l’extrait précédent, addr_epunsa aurait pu aussi bien être une variable
gobale, mais jpr doit être locale. Ainsi
struct Person jpr = {
"Jean-Paul", "Rigault",
FACULTY,
addr_epunsa,
};
c Jean-Paul R IGAULT
V 2.1 – 7 février 2010
4.2. Structures 93
struct Instruction
{
unsigned code_op : enum Op_Code;
unsigned index_reg : 4;
unsigned base_reg : 4;
int offset : 16;
};
adr_epunsa = {
930, "route de Colles",
6903, "Sophia Antipolis",
}; /* ILLEGAL: affectation */
c Jean-Paul R IGAULT
V 2.1 – 7 février 2010
4.2. Structures 95
Affectation de structures
autre_jpr = jpr;
Nous verrons aussi (7.1.3) que les structures peuvent être passées en argu-
ment ou retournées par une fonction.
Retour sur l’affectation des tableaux
On voit que si les tableaux ne sont pas affectables directement (4.1.1), ils le
sont quand on les embarque dans une structure !
Là encore, l’ordre n’a pas d’importance (mais évitez de perdre le lecteur !).
En outre comme la dimension de faculty est laissée libre, le compilateur
la calculera automatiquement (ici 2).
4.3 Unions
Les agrégats de type union sont une des particularités du langage C, bien
que l’on puisse trouver des constructions analogues dans d’autres langages. Ils
permettent de placer à un même emplacement mémoire des objets de types
différents — mais, contrairement aux structures, un seul objet à la fois.
c Jean-Paul R IGAULT
V 2.1 – 7 février 2010
4.3. Unions 97
u.real_view = 3.141592;
i = u.int_view;
tenu du choix des noms, il est assez clair, au moins dans la tête du program-
meur et sans doute aussi du lecteur, que c’est la valeur du champ position
qui détermine la manière d’interpréter le contenu de cette union. Mais, faut-il
le répéter, rien dans la syntaxe ne l’exprime.
La fonction print_person rend cette hypothèse plus explicite grâce à
l’instruction switch. Il est d’ailleurs extrêmement fréquent de voir associés à
une union l’utilisation d’un type énuméré et de son corollaire quasi-inévitable
switch. En outre noter que C requiert une qualification complète du chemin
conduisant à la variante, comme dans
pers.info.faculty_info.CNU_section
Ici .info sélectionne le champ union de pers, .faculty_info sélectionne
l’interprétation du contenu de cette union, et .CNU_section sélectionne un
champ dans la structure correspondante. Ouf !
Enfin, la fonction main contient un cas de test. Notez la manière dont l’objet
a_student est initialisé. Nous utilisons la possibilité de nommer les champs
C99 propres à C99, mais pour ce qui concerne la structure Student qui est simple,
nous nous contentons de l’initialisation d’agrégat ordinaire de C90.
Programme 4.4: Structure avec variante (en C99)
/***** Fichier: struct_variant.c --- ATTENTION: C99 *****/
#include <stdio.h>
c Jean-Paul R IGAULT
V 2.1 – 7 février 2010
4.3. Unions 99
35 union {
struct Faculty faculty_info;
struct Admin admin_info;
struct Student student_info;
} info; // informations specifiques selon statut
40 };
switch(pers.position)
{
case FACULTY :
50 printf("\tenseignant : %s\n",
pers.info.faculty_info.CNU_section);
break;
case ADMIN:
printf("\tadministratif : %s\n",
55 pers.info.admin_info.fonction);
break;
case STUDENT:
printf("\tetudiant : annee %d %s\n",
pers.info.student_info.year,
60 pers.info.student_info.repeater?"redoublant":"");
break;
}
}
65 /* Test simple */
int main()
{
struct Person a_student = {
.last_name = "Glandu", .first_name = "Anthelme",
70 .position = STUDENT,
.info.student_info = {Y2, 1},
};
print_person(a_student);
75 return 0;
}
Soyez cependant conscient que le fait de spécifier les dimensions dans le pro-
totype n’entraine pas de vérification de ces dimensions et qu’il vous appartient
toujours d’assurer que vous passez des matrices de taille correcte (et compatible
avec les dimensions indiquées). En particulier, c’est un excellent exercice de
faire tourner votre programme en passant volontairement des tableaux de taille
incorrecte. Essayez même de l’exécuter plusieurs fois avec les mêmes données
(incorrectes).
% Note
0–60 E
61–70 D
71–80 C
81–90 B
91–100 A
Chiffre Modifieur
1–3 -
4–7
8–0 +
c Jean-Paul R IGAULT
V 2.1 – 7 février 2010
4.4. Exercices du chapitre 4 101
Ce tableau n’est valable que pour les notes de A à D, E ne peut avoir de modi-
fieur !
Exercice 4.4 (Opérations sur les dates) On souhaite représenter les dates par
une structure à trois champs entiers représentant respectivement l’année, le
mois, et le jour du mois afin de faire des opérations arithmétiques.
Les opérations suivantes doivent être réalisées (d, d1 et d2 sont des dates et n
un entier) :
date_valid(d)
Retourne un booléen indiquant si la date est valide : l’année doit être
supérieure ou égale à 1900 (Posix ne gère pas de dates inférieures), le
mois doit être compris entre 1 et 12, et le jour entre 1 et 28, 29, 30 ou 31
selon le mois et l’année...
is_leap(y)
Retourne un booléen indiquant si l’année y est bissextile ; une année est
bissextile si elle est divisble par 4, à moins que ce soit une fin de siècle
auquel cas elle doit être divisble par 400 (1900, 2100 ne sont pas bissextiles,
2000 l’est).
today()
Retourne la date du jour. On utilisera les fonctions time et localtime
de <time.h> pour obtenir cette information.
print_date(d)
Imprime une version humainement lisible de la date comme 23/01/2008.
compare_date(d1, d2)
Retourne 0 si les deux dates sont égales, une valeur négative si la première
est inférieure à la seconde, une valeur positive sinon.
add_date(d, n)
Retourne la date n jours après d ; ainsi add_date(today(), 1) re-
tourne demain.
sub_date(d, n)
Retourne la date n jours avant d ; ainsi sub_date(today(), 1) re-
tourne la veille.
diff_date(d1, d2)
Retourne le nombre algébrique de jours entre d1 et d2. Ainsi la différence
entre aujourd’hui et demain est -1 alors qu’entre demain et aujourd’hui,
c’est +1.
Remarque
Pour la réalisation des fonctions arithmétiques add_date, sub_date et
diff_date n’essayez pas d’être trop malin ! Utilisez des algorithmes simples,
par exemple des boucles jour par jour, en gérant correctement les passages de
mois et d’année.
Exercice 4.5 (Gestion de liste de contacts) Écrire une gestion d’agenda, c’est-
à-dire d’une collection d’individus avec leur nom, prénom, adresse et numéro
de téléphone. Les fonctions suivantes doivent au minimum pouvoir être effec-
tuées :
– recherche sur le nom et le prénom,
c Jean-Paul R IGAULT
V 2.1 – 7 février 2010
Chapitre 5
Pointeurs
Les pointeurs de C sont typés : un pointeur sur un entier n’a pas le même
type qu’un pointeur sur un double ou qu’un pointeur sur une structure ; des
pointeurs sur des structures de types différents n’ont pas le même type. On ne
peut donc pas mélanger des types de pointeurs différents dans les expressions.
La syntaxe de la définition et de la déclaration de pointeurs doit indiquer le
type de l’objet pointé :
type-pointé *identificateur;
Par exemple :
int *pi;
double *px;
struct Person *ptr_pers;
union Real_Int *pu;
que l’étoile est attachée à l’identificateur (même s’il peut y avoir des espaces
entre cette étoile et le nom de l’objet). Ainsi
int *pi, *pj;
c Jean-Paul R IGAULT
V 2.1 – 7 février 2010
5.1. Déclarations et opérations sur pointeurs 105
int *pi = 0;
au lieu de
int *pi = NULL;
int i = 4;
int *pi = &i;
int **ppi = π
&pi est une constante de type « pointeur sur pointeur sur entier » ou encore
« double pointeur sur int », alors que ppi est une variable de ce même type.
La situation est schématisée dans la figure 5.2.
Sur un pointeur double comme ppi on peut appliquer une ou deux fois
l’opérateur d’indirection : *ppi est une référence sur un pointeur sur entier
(int *), alors que **ppi est une référence sur un entier.
Des pointeurs de même type peuvent être comparés pour l’égalité (==) ou
l’inégalité (!=) ; ces mêmes opérateurs permettent de comparer un pointeur,
quel que soit son type, à la valeur NULL.
On peut aussi comparer les pointeurs grâces aux opérateurs <, <=, > et >=.
Cette comparaison n’a pas vraiment de sensla plupart du temps. Cependant
elle a un sens lorsque les deux pointeurs pointent sur un même agrégat, par
exemple un tableau, comme dans l’exemple suivant :
int t[100];
int *p1 = &t[0];
int *p2 = &t[5];
...
if (p1 < p2) {...} /* OUI */
En revanche, dans le cas suivant :
int t1[100];
int *p1 = &t1[0];
int t2[10];
c Jean-Paul R IGAULT
V 2.1 – 7 février 2010
5.1. Déclarations et opérations sur pointeurs 107
Pour un pointeur p sur un type T (ou une expression de type pointeur sur
T) et une expression entière n, l’expression p+n a comme valeur un pointeur
sur T qui pointe sur le nème objet de type T suivant si n > 0 (resp. précédant
si n < 0) celui pointé par p.
Illustrons cette opération par l’extrait de programme suivant :
struct Person *px;
struct Person tab[100];
...
px = &tab[5];
Ici px pointe sur la structure Person qui est la composante d’indice 5 du ta-
bleau tab. Dans ces conditions, px+3 pointe sur le troisième élément de type
struct Person suivant tab[5], c’est-à-dire tab[8]. Et px-2, quant à lui,
pointe sur tab[3].
On voit donc que l’addition d’un entier à un pointeur n’est pas la simple
addition à la valeur de l’adresse mémoire. Il y a en fait une mise à l’échelle en
fonction de la taille de l’objet pointé.
Notons, une fois encore, qu’il n’y a aucune vérification de débordement
d’indice : dans l’exemple précédent, px-10 est certainement une erreur, mais
elle n’est détectée ni à la compilation, ni à l’exécution — sauf option spéciale
de mise au point.
Bien entendu, les cas particuliers d’addition et de soustraction entières re-
présentés par les affectations composées (+= et -=) et les incrémentations et
décrémentations (++ et --) sont applicables aux pointeurs.
Par convention 3 , un pointeur sur void est un pointeur « brut », une adresse
machine. Tout pointeur peut être converti en void* sans perte d’information. Ceci
signifie que si le void* est à nouveau converti dans le type initial, il retrouve
la même valeur (i.e., pointe sur le même objet).
Ces conversions, dans les deux sens, peuvent être implicites ou bien sûr
explicites (cast) comme dans les exemples suivants :
int i = 4;
double x = 3.14;
int *pi = &i;
double *px = &x;
void *pv;
...
pv = pi;
pi = pv; /* OK: pi pointe sur i */
...
pv = (void *)px;
px = (double *)pv; /* OK: px pointe sur x */
Mais attention aux incohérences :
pv = pi; px = pv; /* Catastrophe annoncée !!!! */
Dans le dernier exemple, pv prend la valeur d’un pointeur sur int puis est
converti en pointeur sur double. Si l’on essaie de prendre une référence sur
l’objet maintenant pointé par px (*px), cela revient à interpréter l’entier pointé
par pi comme un double, mais sans conversion ! C’est un problème analogue
à celui des unions (4.3.2) : le programmeur a la responsabilité de garder la
trace du type exact pointé par un pointeur sur void. Il est à noter que ce
type d’erreur est indétectable dans le cas général, à la compilation comme à
l’exécution.
3. Ce ne peut être qu’une convention, puisqu’il n’y a aucun objet de type void.
c Jean-Paul R IGAULT
V 2.1 – 7 février 2010
5.1. Déclarations et opérations sur pointeurs 109
Enfin, et pour des raisons évidentes, les seules opérations permises sur un
void* sont l’affectation et les tests d’égalité et d’inégalité, ainsi que la prise
de l’adresse du pointeur lui-même. Les opérations d’indirection (*), l’addition
et la soustraction d’un entier, la soustraction de deux pointeurs, l’opérateur
-> (5.2.1) ou encore l’indexation (5.3.2) sont interdites pour les void*, car elle
nécessitent toutes la connaissance du type, ou au moins de la taille, de l’objet
pointé.
int *pp;
int *const pconst = &i;
const int *p_sur_const;
const int *pconst_sur_const = ⁣
pp = pconst; OUI:
pconst = pp; NON: affectation interdite
p_sur_const = pp; OUI: restriction d’accés
pp = p_sur_const; NON: élargissement d’accès
p_sur_const = pconst; OUI
p_sur_const = pconst_sur_const; OUI
Noter que dans la première série d’exemples, &i est de type int *const,
alors que &ic est de type const int *const.
Un cas particulier d’initialisation est celui du passage d’arguments à une
fonction étudié en 7.3.2.
c Jean-Paul R IGAULT
V 2.1 – 7 février 2010
5.2. Pointeurs sur structures et unions 111
Compte tenu de la précédence des opérateurs (3.4.1), les parenthèses sont in-
dispensables et l’écriture devient vite pénible. C’est pourquoi C introduit l’opé-
rateur de sélection de champ à travers un pointeur, dénoté ->. L’expression
p->champ désigne une référence sur le champ de la structure ou de l’union
pointée par p ; elle est strictement équivalente à l’expression (*p).champ, mais
moins lourde.
Avec cet opérateur, les exemples précédents s’écrivent de manière plus lè-
gère :
ps->position = ADMIN;
pu->int_view = 3;
Nous verrons dans la sous-section suivante (5.2.2) l’origine et tout l’intérêt de
cette notation.
L’un des exemples les plus évidents de type récursif est celui des listes
simplement chaînées. Le fichier d’inclusion simple_list.h en 5.1 présente
la définition d’un tel type : une liste simplement chaînée de valeurs entières.
Ce fichier contient aussi le prototype de la fonction insert, dont le corps est
présenté plus loin.
La structure List_Cell 4 comporte donc deux champs : une valeur entière
(val) et un pointeur (next) sur un objet de même type soit List_Cell* .
Ceci va permettre de chaîner linéairement de tels objets comme schématisé sur
4. On voit que List_Cell est à la fois le tag et le nom du type défini par typedef. Ceci
est autorisé, comme nous le verrons en 8.1. Noter aussi que le tag est ici obligatoire car le nom
du type défini par typedef n’est pas encore connu à l’intérieur de la définition de structure.
#endif
la figure 5.3. Par convention — mais cette convention est assez universelle —
la liste s’arrête quand le pointeur next vaut NULL. La liste tout entière est
représentable par un pointeur (type List_Cell *) sur son premier élément
(phead).
Remarque: origine de la notation « flêche »
Sur la figure 5.3, on voit l’origine et même l’élégance de la notation ->. En
effet, dans un expression comme
phead->next->next->val
qui désigne la valeur portée par le troisième élément de la liste (le second
ave la valeur 3), les -> correspondent exactement aux flêches du schéma.
La fonction insert est définie dans un autre fichier présenté en 5.2. On doit
bien entendu inclure le fichier simple_list.h (programme 5.1) afin d’impor-
ter la définition du type List_Cell. La directive #include effectue ce travail :
noter l’utilisation des « doubles quotes » ("...") à la place des « piquants »
c Jean-Paul R IGAULT
V 2.1 – 7 février 2010
5.2. Pointeurs sur structures et unions 113
(<...>) qui indiquent ici de chercher ce fichier d’abord dans le répertoire cou-
rant, avant d’examiner les répertoires standards du système (voir 6.2.2).
La fonction insert insère la valeur entière v de son deuxième argument
dans la liste dont la tête est pointée par son premier argument. Le point d’in-
sertion est devant le premier élément de la liste dont la valeur est supérieure ou
égale à v. Ainsi, si la liste était rangée par ordre croissant avant insertion, elle
le demeure après. La liste peut comporter des répétitions. La fonction retourne
un pointeur 5 sur la nouvelle tête de liste. En effet, au cas où v est inférieur à
toutes les valeurs de la liste, c’est la nouvelle cellule qui devient la tête de liste.
Le parcours de liste et l’insertion, schématisés en 5.4, mettent en jeu trois
pointeurs :
1. pcurr visite les cellules l’une après l’autre ;
2. pprev pointe toujours sur la cellule précédant immédiatement celle poin-
tée par pcurr, sauf au début — quand pcurr vaut phead, où il est NULL ;
3. enfin pnew pointe sur une nouvelle cellule de liste qui est allouée dyna-
miquement pour contenir la valeur v.
L’allocation dynamique est effectué par l’appel de la fonction malloc. Cette
fonction reçoit en argument la taille d’un objet, obtient du système l’allocation
d’une zone mémoire suffisamment grande pour contenir un tel objet et retourne
un pointeur sur cette zone (voir 9.1.4). Nous ignorons ici le traitement du cas
d’erreur où la mémoire serait indisponible : malloc retournerait alors la valeur
NULL.
Le parcours de liste s’interrompt soit en fin de liste (pcurr devient NULL),
soit dès que la valeur contenue dans la cellule courante (pcurr->val) devient
supérieure ou égale à v. On note que tout le travail est effectué dans la partie
5. Pour une explication de la déclaration de cette fonction, et en particulier de son type de
retour, voir 7.3.1.
#include <stdlib.h>
5 #include "simple_list.h"
pnew = malloc(sizeof(List_Cell));
pnew->val = v;
15
for (pcurr = phead, pprev = NULL;
pcurr != NULL && v > pcurr->val;
pprev = pcurr, pcurr = pcurr->next)
{
20 /* Vide */
}
if (pprev != NULL)
{
/* La liste n’etait pas vide (phead != NULL) */
30 pnew->next = pcurr;
pprev->next = pnew;
return phead;
}
else if (pcurr != NULL)
35 {
/* La nouvelle cellule devient la tete de liste */
pnew->next = pcurr;
return pnew;
}
40 else
{
/* La liste etait vide (phead == NULL) */
pnew->next = NULL;
return pnew;
45 }
/*NOTREACHED*/
}
c Jean-Paul R IGAULT
V 2.1 – 7 février 2010
5.2. Pointeurs sur structures et unions 115
contrôle de la boucle for (le corps est vide) : c’est évidemment une question
de style et de goût.
Lorsque la boucle s’arrête trois cas sont possibles :
1. si pprev n’est pas NULL on insère pnew 6 juste derrière la cellule pointée
par pprev — noter que pcurr peut être NULL si le nouvel élément de-
vient le dernier de la liste ; la tête de liste est inchangée (égale à phead) ;
2. si pprev est NULL, mais pas pcurr, c’est que pcurr pointe sur le pre-
mier élément de la liste (pcurr est égal à phead) et que pnew devient la
nouvelle tête de liste ;
3. enfin si pprev et pcurr sont tous les deux NULL, c’est que phead l’était
aussi et pnew devient la tête de liste — et aussi la seule cellule.
Enfin, le programme 5.3 sert de test pour la fonction insert. Il lit une liste
de nombres entiers, les insère un à un et imprime le résultat qui doit être une
liste triée. Noter ici que le source du programme tient sur deux fichiers ; pour
obtenir un exécutable, on peut utiliser la commande
% gcc -o simple_list simple_list_main.c simple_list.c
mais il est conseillé d’utiliser make (10.2.2). Ici, on a besoin d’un fichier de nom
makefile (ou Makefile) dans le même répertoire que les fichiers-source. En
voici le contenu (en supposant que vous avez défini les variables d’environne-
ment CC et CFLAGS) :
# Makefile pour la liste simplement chainée
# Dépendances individuelles
simple_list_main.o simple_list.o : simple_list.h
# Nettoyage
clean :
T A B -rm *.o simple_list
#include <stdio.h>
5 #include "simple_list.h"
int main()
{
List_Cell *phead = NULL;
10 List_Cell *p;
int n;
printf("Liste triee:\n");
for (p = phead; p != NULL; p = p->next)
printf("%d ", p->val);
20 putchar(’\n’);
return 0;
}
c Jean-Paul R IGAULT
V 2.1 – 7 février 2010
5.2. Pointeurs sur structures et unions 117
Programme 5.4 – Insertion dans une liste avec un seul (double) pointeur
/***** Fichier: Simple_List/simple_list_double_ptr.c *****/
#include <stdlib.h>
#include <stdio.h>
5
typedef struct List_Cell
{
int val;
struct List_Cell *next;
10 } List_Cell;
pnew = malloc(sizeof(List_Cell));
pnew->val = v;
pnew->next = *pp;
*pp = pnew;
}
30
int main()
{
List_Cell head = {0, NULL};
List_Cell *p;
35 int n;
c Jean-Paul R IGAULT
V 2.1 – 7 février 2010
5.3. Pointeurs et tableaux 119
le pointeur ppers pointe sur une zone de mémoire pouvant accueillir un ta-
bleau de 10 structures Person. On peut désigner les différentes composantes
sous la forme ppers[0], ppers[1]... On pourra donc écrire des instructions
comme
ppers[i].age = 18;
c Jean-Paul R IGAULT
V 2.1 – 7 février 2010
5.3. Pointeurs et tableaux 121
Exercice 5.1 (Liste triée simplement chaînée) Compléter le type « liste d’en-
tiers triée par ordre croissant » en définissant les fonctions suivantes :
– chercher si une valeur est dans la liste ;
– retourner le nième élément, s’il existe ;
c Jean-Paul R IGAULT
V 2.1 – 7 février 2010
5.4. Exercices du chapitre 5 123
– détruire une valeur dans la liste, si elle s’y trouve (une seule occurrence et
toutes les occurences) ;
– fusionner une liste avec une autre, c’est-à-dire ajouter à la première liste une
copie des valeurs de la seconde ; bien entendu la liste finale doit aussi être
triée ;
– dupliquer une liste, c’est-à-dire retourner une nouvelle liste dont les valeurs
sont des copies des valeurs de la première liste ;
– imprimer une liste.
Exercice 5.2 (Liste triée doublement chaînée) Même exercice que précédem-
ment, mais avec des listes doublement chainées. La cellule de liste a donc la
forme suivante :
struct DList_Cell
{
int val;
struct DList_Cell *prev; /* pointeur arriere */
struct DList_Cell *next; /* pointeur avant */
};
Par ailleurs, l’implémentation des listes avec une fin marquée par un pointeur
nul comme dans l’exercice 5.1 est déjà maladroite pour des listes simplement
chaînée mais elle est carrément débile pour des listes doubles. Vous utiliserez
donc l’implémentation décrite dans la figure 5.8. Ici la liste contient toujours
une cellule de garde (dont la valeur ne fait pas partie de la liste). Lorsque
la liste est vide, les deux pointeurs de cette cellule pointent sur elle-même
(figure 5.8(a)). Sinon, les cellules sont chaînées naturellement, et la liste re-
boucle sur la cellule de garde. Si phead est l’adresse de la cellule de garde
(figure 5.8(b)), la première cellule « utile » est donc pointée par phead->next
et la liste se termine à la cellule phead exclue.
Bien entendu, un léger inconvénient par rapport à l’implantation directe de
l’exercice 5.1 est que vous avez maintenant besoin de définir une fonction d’ini-
tialisation de liste pour créer la cellule de garde et vous ne devez pas oublier
d’invoquer cette fonction avant de manipuler le liste.
Remarque sur le test de ce programme
Un bon moyen pour (commencer à) tester ce programme est de prendre comme
programme de test exactement le même que pour l’exercice 5.1, de récupérer
les sorties des deux programmes dans des fichiers et de vérifier que ces fichiers
sont identiques, par exemple en utilisant les commandes cmp ou diff du shell.
Dans le même esprit (et sous la même hypothèse), écrire (et testez !) les fonc-
tions suivantes (s, s1 et s2 désignent des chaînes, i et n des entiers, c un
caractère) :
string_trim(s)
Enlève les espaces initiaux et finaux de la chaîne.
substring(s, i, n)
Retourne la sous-chaîne de s de longueur maximale n commençant à
l’indice i ; si n est trop grand, on ne retourne que ce qui est possible.
substring_search(s1, s2)
Recherche la première occurrence de la chaîne s1 dans s2 et retourne
l’index correspondant dans s2 ou -1 (si s1 n’est pas une sous-chaîne de
s2).
insert_substring(s1, s2, i)
Insère la chaîne s1 devant le caractère d’indice n de s2 ; ne fait rien si n
est invalide.
c Jean-Paul R IGAULT
V 2.1 – 7 février 2010
5.4. Exercices du chapitre 5 125
delete_substring(s, i, n)
Détruit la sous-chaîne de s de longueur maximale n commençant à l’in-
dice i ; si n est trop grand, on ne détruit que ce qui est possible.
replace_substring(s1, i, n, s2)
Remplace la sous-chaîne de s1 de longueur maximale n commençant à
l’indice i par la chaîne s2; si n est trop grand, on ne remplace que ce qui
est possible.
tokenize_string(s, c, ts[], n, empty_fields)
Étant donnée une string s composée de champs séparés par le caractère
c, copie ces champs un par un dans les éléments du tableau de chaînes
ts et retourne le nombre de champs ; n est la taille maximale du tableau
ts.
Si le booléen empty_fields est vrai et si deux séparateurs c sont ad-
jacents, la chaîne correspondante de ts doit être la chaîne vide. Ainsi
après
char *ts[10];
char s[] = "/bin:/usr/bin::/usr/local/bin:/home/jpr/bin";
int n = tokenize_string(s, ’:’, ts[], 10, b);
Exercice 5.4 (Tri d’un tableau de pointeurs) Modifier le tri par insertion du
programme 2.6 pour trier des chaînes de caractères.
Exercice 5.5 (Commande fgrep) Utilisez les fonctions de l’exercice 5.3 pour
écrire une version simplifiée de la commande fgrep. Cette commande reçoit
une chaîne de caractère en argument, lit l’entrée standard et en affiche toutes
les lignes qui contiennent la chaîne.
Remarque
En quoi la commande fgrep fournie par le système est-elle supérieure à la
votre ?
L’arbre complet est bien entendu défini par un pointeur sur sa racine et a la
forme décrite dans la figure 5.9.
Ecrire la procédure d’insertion suivante pour un entier n donné : si la valeur
portée par la racine est inférieure (resp. supérieure) à n, insérer la valeur n dans
le sous-arbre de gauche (resp. de droite) ; si les deux valeurs sont égales, ne rien
insérer ; cette procédure est appliquée récursivement sur les sous-arbres.
Ensuite écrire une fonction de recherche qui explore l’arbre pour trouver une
valeur donnée et retourne un pointeur sur la cellule correspondante (ou bien
le pointeur nul si la valeur n’est pas dans l’arbre). Enfin, écrivez une fonction
d’impression de l’arbre « en profondeur d’abord » : à tire d’exemple, l’arbre de
la figure 5.10 devra être imprimé ainsi :
10
-6
--3
---EMPTY
c Jean-Paul R IGAULT
V 2.1 – 7 février 2010
5.4. Exercices du chapitre 5 127
---EMPTY
--9
---7
----EMPTY
----EMPTY
---EMPTY
-12
--EMPTY
--14
---EMPTY
---17
----EMPTY
----EMPTY
Exercice 5.8 (Pathologie des pointeurs) Les pointeurs fournissent des possibi-
lités très puissantes mais sont aussi une des principales cause d’erreur dans les
programmes C. Parmi les erreurs fréquentes :
– Utiliser l’objet pointé par un pointeur sans que ce pointeur ait été initialisé
(ou après qu’il ait été initialisé à NULL).
– Se tromper lors de l’arithmétique des pointeurs en débordant les limites et
en pointant ainsi sur des objets de type différent de ce que l’on croit.
– Ne pas libérer la mémoire allouée par malloc lorsque l’on n’en a plus besoin
(« fuite de mémoire »).
En particulier, lorsque l’on utilise une fonction (de bibliothèque standard ou
pas) dont certains des paramètres sont des pointeurs, on doit toujours se po-
ser les questions suivantes (et obtenir la réponse) : le paramètre peut-il être un
simple pointeur (possiblement non initialisé) ? ou doit-il être un pointeur sur
un objet effectivement alloué ? et dans ce cas, est-ce un pointeur sur un objet
individuel ou sur un tableau ? Si la fonction retourne un (ou plusieurs) poin-
teurs, il faut également toujours se demander si les objets correspondants ont
été alloués et comment (i.e., par malloc ou autre ?).
Testez la robustesse des fonctions que vous avez réalisées lors de l’exercice 5.3
par rapport à ces problèmes.
c Jean-Paul R IGAULT
V 2.1 – 7 février 2010
Chapitre 6
Le préprocesseur ANSI C
1. qui peut être constituée de plusieurs lignes physiques, si l’on précède chaque fin de ligne
du caractère \
c Jean-Paul R IGAULT
V 2.1 – 7 février 2010
6.3. Définition de macros 131
La forme
#include "nom-de-fichier"
quant à elle, commence par chercher dans le répertoire où se trouve le fichier source
qui comporte cette directive d’inclusion 2 . Si le fichier n’est pas trouvé, le fonc-
tionnement est identique à la forme avec des « piquants » (<...>).
int main()
{
printf(MESSAGE "%d\n", NBUF);
}
donne après passage à travers le préprocesseur
int t[512 ];
int u[512 *2 ];
int main()
{
printf("NBUF = " "%d\n", 512 );
}
A noter que la ligne contenant printf deviendra
printf("NBUF = %d\n");
après concaténation des chaînes littérales adjacentes.
On remarque aussi que la macro NBUF est correctement expansée dans la
définition de NBUF2.
c Jean-Paul R IGAULT
V 2.1 – 7 février 2010
6.3. Définition de macros 133
se lit bien
x = 2 + (Max(y, z));
alors
x = 2 + BAD_MAX(y, z);
est expansé en
x = 2 + y > z ? y : z;
« Stringification » : #
Dans une macro avec arguments, un argument formel n’est pas remplacé
si dans la chaine définissante il figure à l’intérieur d’une chaîne littérale. Par
exemple, dans
#define message(a) "le message est: a"
...
printf(message(bonjour));
la dernière ligne est expansée en
printf("le message est: a");
ce qui n’est pas forcément ce qu’on veut.
L’opérateur unaire # appliqué à un argument de macro en effectue la strin-
gification 4 c’est-à-dire transforme cet argument en chaîne littérale. Ainsi, avec
#define message(a) "le message est: " #a
...
printf(message(bonjour));
la dernière ligne sera expansée en
printf("le message est: " "bonjour");
qui deviendra
printf("le message est: bonjour");
après concaténation des chaînes littérales adjacentes.
4. Je manque d’imagination pour trouver une traduction française !
c Jean-Paul R IGAULT
V 2.1 – 7 février 2010
6.4. Compilation conditionnelle 135
lignes-si-faux
#endif
On évalue l’expression-statique qui est constituée à l’aide des opéra-
teurs habituel de C et sa valeur de vérité détermine les lignes source que l’on
considère. Cette valeur de vérité doit bien entendu être évaluable à la compila-
tion.
#define NBUF 512
#define NBUF2 NBUF*2
...
#if NBUF > 512 && NBUF2 > 1024
int t[NBUF];
int u[NBUF2];
#else
int t[512];
int u[1024];
#endif
Bien entendu, la clause #else est optionnelle. En outre la clause #elif
permet d’enchaîner les conditions :
#if NBUF < 512
...
#elif NBUF < 1024
...
#elif NBUF < 2048
...
#else
...
#endif
La encore, la clause #else est optionnelle.
c Jean-Paul R IGAULT
V 2.1 – 7 février 2010
6.5. Autres directives du préprocesseur 137
#if !defined(getchar)
int getchar(void);
#endif
s’écrit aussi
#ifndef getchar
int getchar(void);
#endif
c Jean-Paul R IGAULT
V 2.1 – 7 février 2010
6.6. Exercices du chapitre 6 139
c Jean-Paul R IGAULT
V 2.1 – 7 février 2010
Chapitre 7
Fonctions
O n décrit ici en détail la notion de fonction que nous avons déjà largement
utilisée dans les chapitres précédents. La fonction est l’un des mécanismes
principaux de modularité en C ; c’est aussi l’une des bases de la programmation
structurée.
En ansi C, chaque fonction a un type, déterminé à la fois par le nombre et
le type de ses arguments et par celui de sa valeur de retour. Comme pour tout
objet, ce type doit être connu avant toute utilisation. Trois contextes différents
sont associés à la notion de fonction :
1. la déclaration d’une fonction, qui en définit le type et qui est mise en œuvre
en ansi C, grâce à la notion de prototype ;
2. la définition d’une fonction, qui reprend son type, mais surtout en définit
le corps, c’est-à-dire la liste des instructions exécutées à chaque appel ;
3. l’appel — ou l’invocation — de la fonction.
int tab[100];
raz(100, tab);
Il n’y a pas de contradiction : un nom de tableau est une constante de type
pointeur, et ce pointeur est passé par valeur. En fait, le prototype de raz peut
tout aussi bien s’écrire :
void raz(int n, int *t);
et l’appel
raz(100, &tab[0]);
c Jean-Paul R IGAULT
V 2.1 – 7 février 2010
7.2. Déclaration, définition et appel de fonction 143
char str[100];
string_copy(str, "bonjour");
Rappelons que la fonction doit avoir un moyen de déterminer le nombre
d’éléments du tableau. C’est pourquoi, dans le cas de la fonction raz, ce
nombre (100) est passé en argument. Ce n’est pas nécessaire pour les chaînes
de caractères puisque la convention de les terminer par le caractère nul permet
d’en connaitre la longueur.
Le passage par valeur s’applique aussi quand une structure ou une union
est en argument :
void print_person(struct Person who);
Ils ne jouent alors pas d’autre rôle que celui de faciliter la lisibilité en auto-
documentant le source ; pour le reste, ils sont ignorés.
On peut avoir dans un fichier source plusieurs prototypes pour la même
fonction, du moment qu’ils sont identiques (aux noms éventuels des arguments
formels près).
est interdit.
Pour écrire le corps d’une fonction dont la liste d’arguments est élidée, il
faut pouvoir trouver quelque part une information sur le type des arguments.
Dans le cas de printf, ce sont les spécifications de format, contenues dans
le premier argument, qui jouent ce rôle. Une fois ces types connus, les macros
standards d’environnement définies dans le fichier <stdarg.h> (voir 9.6.1)
permettent de décomposer ces listes variables d’argument.
Compatibilité avec C traditionnel et C++
Pour des raisons de compatibilité arrière, en ansi C, un prototype comme
int f();
est équivalent à
int f(...);
et non pas à int f(void). Ceci est une cause d’incompatibilité avec C++
où int f() est le prototype d’une fonction sans argument.
c Jean-Paul R IGAULT
V 2.1 – 7 février 2010
7.2. Déclaration, définition et appel de fonction 145
int i;
struct Person somebody;
f(i); /* f((int)i): troncature */
g(&somebody); /* g((void *)&somebody) */
j = Max(i++, f(i));
est non portable : on ne sait pas si f est invoqué après que i ait été incré-
menté, ou avant.
c Jean-Paul R IGAULT
V 2.1 – 7 février 2010
7.3. Fonctions et pointeurs 147
la sanction sera sévère : l’objet local tmp n’existe plus après le retour de la
fonction ! La plupart des compilateurs donneront un « warning » dans des
cas aussi simples, mais ce n’est pas toujours possible en général.
On veut réaliser une fonction qui échange deux entiers. Les entiers ne
peuvent pas être passés par valeur car leur changement de valeur dans le corps
de la fonction serait sans effet sur les arguments effectifs :
void bad_swap(int i, int j)
{
int temp = i;
i = j; // les variables locales sont bien echangees...
j = temp; // mais pas les parametres effectifs !
}
On doit donc utiliser des pointeurs :
void swap(int *pi, int *pj)
{
int temp = *pi;
*pi = *pj;
*pj = temp;
}
et lors de l’appel, on doit faire attention à prendre les adresses des objets à
échanger :
int i, j;
swap(&i, &j);
Si c’est un pointeur qui doit être modifié, il faut utiliser un double pointeur
comme dans la fonction alloc_mem suivante 2 :
void alloc_mem(size_t sz, void **ptr)
{
if ((*ptr = malloc(sz)) == NULL)
{
fprinft{stderr, "Alloc_Mem: memoire saturee\n");
exit(1);
}
}
Noter que le compilateur effectue dans le cadre des appels de fonctions les
mêmes vérifications que celles évoquées en 5.1.5. Ainsi, dans
void f(struct Person *);
Supposons que l’on souhaite passer une fonction en argument à une autre
fonction. Par exemple, on a écrit un programme d’intégration numérique qui
calcule
Z b
f ( x )dx
a
c Jean-Paul R IGAULT
V 2.1 – 7 février 2010
7.3. Fonctions et pointeurs 149
double (*pf)(double)
signifie qu’une expression comme
(*pf)(3.14)
est un réel double ; donc *pf est une fonction à un argument double qui
retourne un double ; et finalement, pf est bien un pointeur sur une telle
fonction.
Un pointeur sur fonction est un type (scalaire) comme un autre et, comme
tel, peut être soumis à n’importe quelle construction de type. On peut en dé-
finir des tableaux, il peut être le type de retour d’une fonction, il peut être un
champ de structure ou d’union. Le programme 7.1 illustre quelques unes de
ces possibilités.
Dans ce programme, on trouve quelques types intéressants 3 :
– funct_tab est un tableau de (3) pointeurs sur fonction à argument
double et résultat double ;
– funct_names est un tableau de (3) structures ; le premier champ de cette
structure est une chaîne de caractères (banal !), et le second un double
pointeur sur fonction (à argument double et résultat double) ;
– La fonction select n’a pas d’argument et retourne un pointeur sur fonc-
tion (à argument double et résultat double) choisie au hasard ;
– La fonction main choisit une fonction à exécuter grâce à la fonction
select ; elle applique cette fonction à la valeur π, puis cherche dans la
table funct_names le nom de la fonction qui a été choisie en comparant
la valeur du pointeur pcurrf au contenu du champ ppf.
3. On peut essayer de réécrire ce programme avec des typedef pour le rendre plus lisible.
Programme 7.1 – Les pointeurs sur fonction dans tous leurs états
/***** Fichier: ptr_fonct.c *****/
#include <stdlib.h>
#include <stdio.h>
5 #include <math.h>
struct Funct_Name
10 {
const char *name;
double (**ppf)(double); /* double pointeur sur fonction */
} funct_names[] = {
{"sinus", &funct_tab[0]},
15 {"cosinus", &funct_tab[1]},
{"tangente", &funct_tab[2]},
};
const int N_FUNCT_NAMES =
sizeof(funct_names)/sizeof(struct Funct_Name);
20
double (*select(void))(double)
{
extern long int random(void);
30 int main()
{
double (*pcurrf)(double);
double res;
int i;
35
while (1)
{
pcurrf = select();
res = pcurrf(PI);
40 for (i = 0; i < N_FUNCT_NAMES; i++)
{
if (*funct_names[i].ppf == pcurrf)
{
printf("Fonction: %s Resultat: %g\n",
45 funct_names[i].name, res);
break;
}
}
}
50 }
c Jean-Paul R IGAULT
V 2.1 – 7 février 2010
7.4. La fonction main 151
#include <stdio.h>
#include <ctype.h>
5
int no_new_line = 0;
int upper_case = 0;
int lower_case = 0;
c Jean-Paul R IGAULT
V 2.1 – 7 février 2010
7.5. Exercices du chapitre 7 153
Exercice 7.1 (Intégration par la méthode des trapèzes) Écrire une fonction
d’intégration par la méthode des trapèzes invocable de la manière suivante :
s = integr(a, b, f, delta_x);
Exercice 7.2 (Efficacité des fonctions inline) Il n’est pas sûr que l’optimisation
-O ne soit pas meilleure... quand elle est possible.
c Jean-Paul R IGAULT
V 2.1 – 7 février 2010
Chapitre 8
int var; /* E1 */
5 typedef struct st /* E3 */
{
int st; /* E4 */
} st; /* E1 */
10 struct var /* E3 */
{
double st; /* E4 */
double var; /* E4 */
};
15
enum e /* E3 */
{
A, /* E1 */
B /* E1 */
20 } x; /* E1 */
struct A /* E3 */
{
int A; /* E4 */
25 };
int main() /* E1 */
{
goto var; /* E2 */
30
var: /* E2 */
st: /* E2 */
A: /* E2 */
B: /* E2 */
35 goto A; /* E2 */
c Jean-Paul R IGAULT
V 2.1 – 7 février 2010
8.2. Structure des programmes C 157
c Jean-Paul R IGAULT
V 2.1 – 7 février 2010
8.3. Durée de vie et règles de visibilité 159
Objet locaux
3. En fait trois, car on a aussi la visibilité de fonction applicable aux étiquettes (voir 8.3.6)
4. Ce terme de visibilité de « fichier » est passablement impropre : il vaudrait mieux parler
de visibilité d’unité de compilation.
{
int a; /* Objet local automatique */
static int n; /* Objet local statique (remanent) */
register int r; /* Objet local automatique */
auto int b; /* Objet local automatique */
...
}
Un objet rémanent (statique local) comme n conserve sa valeur d’une entrée
du bloc à l’autre. Ainsi la fonction suivante conserve dans une variable locale
rémanente le nombre de fois où elle a été appelée :
int f(int i)
{
static int nb_calls = 0;
nb_calls++;
...
}
Objets globaux
void f()
{
...
}
Des objets privés de même nom, mais dans des fichiers source différents
n’entrent pas en conflit.
Comme les objets externes sont potentiellement visibles dans tout le pro-
gramme, à cause de la compilation séparée, chaque fichier source doit en
connaitre le type ; ceci est réalisé par une déclaration d’externe comme par
exemple :
c Jean-Paul R IGAULT
V 2.1 – 7 février 2010
8.3. Durée de vie et règles de visibilité 161
c Jean-Paul R IGAULT
V 2.1 – 7 février 2010
8.3. Durée de vie et règles de visibilité 163
static double z;
static int z[10];
double f2(int x) extern int vv;
{ extern int f1(int, int);
register int i;
int v; double g(void)
/* ... */ {
} /* ... */
}
10 struct B b;
int main()
{
typedef struct A /* Local */
15 {
int a;
} A;
void f()
{
25 typedef struct A /* Local */
{
double a;
} A;
A a;
30 struct A b;
c Jean-Paul R IGAULT
V 2.1 – 7 février 2010
8.4. Programmation modulaire en C 165
/*
* Quelques fonctions inline d’usage general et
* destinees a etre utilisees dans de nombreux modules.
*/
#endif
Enfin, noter que comme tout fichier .h, mais encore plus que pour les autres
un fichier d’interface doit être protégé en inclusion unique (6.4.2).
c Jean-Paul R IGAULT
V 2.1 – 7 février 2010
8.5. Exercices du chapitre 8 167
5 #ifndef _Stack_h_
#define _Stack_h_
15 #endif
% Stack
Taille de la pile? 10
Entrez un nombre: 1
Entrez un nombre: 2
Entrez un nombre: 3
Entrez un nombre: 4
Entrez un nombre: 5
Entrez un nombre: 6
Entrez un nombre: 7
Entrez un nombre: 8
Entrez un nombre: 9
Entrez un nombre: 10
Resultat: 10 9 8 7 6 5 4 3 2 1
%
Exercice 8.2 (File Ecrire un module fifo (c’est-à-dire une file « premier
FIFO )
entré, premier sorti ») à la manière du module « pile d’entiers ».
5 #include <stdio.h>
#include <stdlib.h>
#include "Stack.h"
45 int stack_is_full(void)
{
return stack_top == stack_size;
}
50 int stack_is_empty(void)
{
return stack_top == 0;
}
c Jean-Paul R IGAULT
V 2.1 – 7 février 2010
8.5. Exercices du chapitre 8 169
5 #include <stdio.h>
#include "Stack.h"
int main()
{
10 int n = 10;
printf("Resultat: ");
while (!stack_is_empty())
20 printf("%d ", stack_pop());
putchar(’\n’);
return 0;
}
c Jean-Paul R IGAULT
V 2.1 – 7 février 2010
Chapitre 9
La bibliothèque standard
O n ne prétend pas ici donner une description complète de toutes les fonc-
tions de la bibliothèque standard de C. Pour cela, il vaut mieux se reporter
aux pages du manuel (commande man) ou a un site en ligne : celui de Wikipe-
dia est un bon point de départ (cherchez "c standard library manual").
Le livre de Prata [3] contient également une description très complète de la
bibliothèque standard.
c Jean-Paul R IGAULT
V 2.1 – 7 février 2010
9.3. Fonctions mathématiques 173
9.6 Divers
c Jean-Paul R IGAULT
V 2.1 – 7 février 2010
9.6. Divers 175
double sqrt(double x)
{
if (x < 0)
longjmp(env, 1);
int main()
{
double t[] = {1.5, 3.5, -2.5, 4.0};
const int N = sizeof(t) / sizeof(t[0]);
Macro Opérateur
and &&
and_eq &=
bitand &
bitor |
compl ~
not_eq !=
or ||
or_eq |=
xor ^
xor_eq ^=
Préférer
if (not p and i < N and p[i] not_eq 0) ...
à
if (!p && i < N && p[i] != 0) ...
est évidemment une question de gout et de couleur ! Ma propre préférence va
en fait à
if (p != 0 and i < N and p[i] != 0) ...
c Jean-Paul R IGAULT
V 2.1 – 7 février 2010
Chapitre 10
Environnement de développement
10.1 Compilateurs
Qu’ils soient libres ou propriétaires, il existe un grand nombre de compila-
teurs pour le langage C. Wikipedia 1 en liste la plupart.
Il est à noter que le célèbre Visual C++ de Microsoft 2 n’est pas, dans ses
versions récentes tout au moins, un compilateur C99.
La suite gcc 3 , le gnu c compiler, est universellement répandue. Elle pos-
sède un certain nombre d’avantages :
– elle est libre, sous licence gpl 4 ;
– il s’agit non pas d’un simple compilateur d’un langage donné, mais d’un
multi-compilateurs, supportant plusieurs langages différents : C et C++,
Objective C et C++, Ada, Fortran, Java...
– la suite gcc évolue de manière très efficace et réactive ce qui lui permet
de suivre de très près, voire d’anticiper, le travail de normalisation de ces
différents langages ;
– elle produit du code pour virtuellement tout processeur et la plupart des
systèmes disponibles sur le marché ;
– sa qualité globale n’est plus à démontrer (même si certains compilateurs
propriétaires peuvent être supérieurs sur certains points particuliers : e.g.,
la suite de compilateurs Intel et l’architecture multi-cœurs) ;
1. http://en.wikipedia.org/wiki/List_of_compilers
2. http://msdn.microsoft.com/en-us/library/60k1461a(VS.80).aspx
3. http://gcc.gnu.org/
4. http://www.gnu.org/licenses/gpl.html
c Jean-Paul R IGAULT
V 2.1 – 7 février 2010
10.2. Développement traditionnel sous Unix 179
par défaut. Par exemple, si vous programme tient dans un seul fichier source,
disons prog.c et que vous avez correctement définit les variables d’environ-
nement CC et CFLAGS, vous n’avez même pas besoin de makefile ; tapez sim-
plement
% make prog
gcc -g -Wall -std=c99 -o prog prog.c
%
Par ailleurs, si vous n’êtes pas sûr de vos dépendances entre fichiers, le
compilateur gcc vous aidera : utilisez l’option -MM sur la liste des unités de
compilation (fichiers *.c) et il vous fournira ces dépendances sous une forme
telle que vous n’aurez plus qu’à la couper-coller dans votre makefile :
% gcc -MM *.c
random_list.o: random_list.c
simple_list.o: simple_list.c simple_list.h
simple_list_double_ptr.o: simple_list_double_ptr.c
simple_list_main.o: simple_list_main.c simple_list.h
%
c Jean-Paul R IGAULT
V 2.1 – 7 février 2010
10.2. Développement traditionnel sous Unix 181
c Jean-Paul R IGAULT
V 2.1 – 7 février 2010
10.3. Environnements intégrés de développement 183
c Jean-Paul R IGAULT
V 2.1 – 7 février 2010
10.4. Remarque finale 185
c Jean-Paul R IGAULT
V 2.1 – 7 février 2010
10.4. Remarque finale 187
c Jean-Paul R IGAULT
V 2.1 – 7 février 2010
Chapitre 11
11.1 Préprocesseur
11.1.1 Macros prédéfinies
11.1.2 Macros à nombre variable d’arguments
Tableaux incomplets
c Jean-Paul R IGAULT
V 2.1 – 7 février 2010
Chapitre 12
c Jean-Paul R IGAULT
V 2.1 – 7 février 2010
Bibliographie
[1] von Hagen, W. The Definite Guide to GCC, 2e édition. Apress, 2006.
[2] ansi Committee x3j11. Rationale for the ansi C Programming language.
Silicon Press, 1990.
[3] Prata, S. C Primer Plus, 5e édition. Sams Publishing, 2005.
[4] Prata, S. C++ Primer Plus, 4e édition. Sams Publishing, 2001.
[5] Flipo, D. Documentation sur le module frenchb de babel. Site Web,
http://daniel.flipo.free.fr/frenchb/frenchb2-doc.pdf,
2007.
[6] Joseph, N. Divers articles sur C et C++. Site Web,
http://nicolasj.developpez.com/.
[7] Braams, J. Babel, a multilingual package for use
with LATEX’s standard document classes. Site Web,
http://css.ait.iastate.edu/Tex/Sp/babel.pdf, 2005.
[8] Kochan, S. G. Programming in C, 3e édition. Sams Publishing, 2005.
[9] Tribble, D. Incompatibilities between iso C and iso C++. Site Web,
http://david.tribble.com/text/cdiffs.htm.
[10] Lamport, L. LATEX, a Document Preparation System. Addison-Wesley, 1986.
[11] Lippman, S. B., Lajoie, J. C++ Primer, 3e édition. Addison Wesley, 1998.
[12] Delannoy, C. Langage C, 4e édition. Eyrolles, 1999.
[13] Dijkstra, E. W. Go to statement considered harmful. Communications of
the ACM 11, 3 (March 1968), 147–148. Version annotée par David Tribble
en http://david.tribble.com/text/goto.html.
[14] Institut für Theoretische Informatik at TH-
Darmstadt. xindy, a flexible indexing system. Site Web,
http://xindy.sourceforge.net/.
[15] Valgrind Developpers. Valgrind. Site Web, http://valgrind.org/.
[16] Kernighan, B. W., Ritchie, D. M. The C Programming Language, 1re édi-
tion. Software Series. Prentice Hall, 1978.
[17] Kernighan, B. W., Ritchie, D. M. The C Programming Language (ansi C
edition, 2e édition. Software Series. Prentice Hall, 1988.
[18] Kernighan, B. W., Ritchie, D. M. Le langage C : norme ansi, 2e édition.
Sciences Sup. Dunod, 2004.
[19] gnu Project. Gdb: The gnu project debugger. Site Web,
http://sourceware.org/gdb/.
c Jean-Paul R IGAULT
V 2.1 – 7 février 2010
Glossaire
c Jean-Paul R IGAULT
V 2.1 – 7 février 2010
GLOSSAIRE 197
3. Lorsque les deux versions, C traditionnel et ANSI C, coexistent, cc est souvent la com-
mande de compilation en C traditionnel. Sur notre système, la commande de compilation
ANSI C est gcc.
4. Dans ces exemples d’utilisation de commande, nous faisons l’hypothèse que le shell utilisé
est celui de Steve Bourne (sh) ou lui est compatible (comme ksh, le shell dû à David Korn).
Ce que tape l’utilisateur est en police penchée alors que ce qu’affiche le système est en
police normale. Le caractère % est l’« invite » (le prompt) du shell.
compilation séparée
Lorsque le texte d’un même programme est reparti dans plusieurs fichi-
ers-sources différents, chacun de ces fichiers est traité séparément par
le compilateur. Ceci signifie que chaque fichier-source doit contenir toute
l’information nécessaire au compilateur, et en particulier toute celle qui
permet l’analyse de sémantique statique (typage).
constante
Un objet qui ne peut être modifié.
constante littérale
Un objet qui ne peut être modifié et dont la valeur peut être évaluée
lors de la compilation (on parle aussi de constante lexicale, ou encore
statique). Ainsi en C, 12, -4, "bonjour" sont des constantes littérales.
contrôle (flot de contrôle, instruction de contrôle)
Le flôt de contrôle est déterminé par l’ordre d’exécution des instructions.
En programmation procédurale Ce flot est en général séquentiel (les ins-
tructions sont exécutées dans l’ordre où elles sont écrites) mais cette sé-
quentialité peut être brisée par les instructions de contrôle qui sont le plus
souvent de trois types:
– sélection (if et switch en C),
– boucles (while, do ... while et for),
– la rupture de séquence (goto).
corps d’une fonction
Voir fonction.
corps d’un module
La partie du module contenant la définitions des données et des fonctions
du module. Certaines de ces données et de ces fonctions peuvent être
privées au module (encapsulation) et seront donc invisibles dans le reste
du programme. Les autres sont exportées dans les parties du programmes
qui voient les déclarations contenues dans la spécification du module.
déclaration d’un objet
La déclaration d’un objet indique simplement le type de cet objet au
compilateur. Il peut en général y avoir autant de déclarations que l’on
veut pour un même objet du moment qu’elles sont identiques (ou com-
patibles).
définition d’un objet
La définition d’un objet, outre qu’elle indique aussi son type, réserve et
éventuellement initialise la zone mémoire correspondant à l’objet. Il y a
une et une seule définition dans tout le programme pour chaque objet.
dynamique
Une notion quelconque est dynamique si elle ne peut être vérifiée, éva-
luée, utilisée... qu’à l’exécution. Sinon, elle peut l’être à la compilation et
est dite alors statique.
édition de liens
En cas de compilation séparée, l’éditeur de liens rassemble tous les
fichiers-objets en un fichier binaire exécutable unique. C’est donc lui qui
résoud les références externes. Certaines de ces objet externes peuvent
être définis dans des bibliothèques.
c Jean-Paul R IGAULT
V 2.1 – 7 février 2010
GLOSSAIRE 199
langage-machine
Le langage des instructions et des données interprétables directement par
le matériel. Il constitue le contenu du binaire exécutable.
library
Voir bibliothèque.
lvalue
Voir référence.
modularité
La possibilité de découper le source d’un programme en unités syntaxi-
quement indépendantes regroupant chacune des données et les fonctions
de manipulation directes de ces données.
La modularité désigne aussi la qualité d’un programme ainsi découpé.
La modularité en C fait l’objet du chapitre 8.
module
Une unité syntaxique de modularité. Un module comporte en général
deux parties:
– la spécification, qui indique les déclarations des objets et les définitions
des types que le module rend visible (qu’il exporte),
– le corps qui contient les définitions de ces objets.
Les parties du programme qui veulent utiliser un module doivent en voir
la spécification (elles l’importent).
En C, le corps d’un module est contenu dans un fichier-source .c, alors
que sa spécification est en général dans un fichier d’entête (de suffixe
conventionnel .h). L’importation est effectuée grâce à la directive d’inclu-
sion de fichier du préprocesseur.
nom (d’un objet)
Dans un langage de programmation, la plupart des objets sont désignés
par un nom représenté par un identificateur. Un identificateur constitue
une référence sur l’objet.
Cependant, certains objets sont anonymes: ils peuvent être temporaires
(créés automatiquement par le compilateur), ou alors alloués dynami-
quement et accessibles uniquement au travers de pointeurs.
objet
Un objet 5 est en fait une zône de mémoire, qu’il s’agisse de mémoire de
données ou de mémoire d’instruction.
En C, un objet peut être une constante, une variable ou une fonction;
chaque objet a un type qui doit être connu à la compilation avant toute
utilisation; un objet à aussi une valeur (le contenu de la zône mémoire).
objet (fichier-objet)
Voir fichier-objet.
optimisation de code
C’est la dernière phase, en général optionnelle, de la compilation.
5. Cette notion d’objet est plus large que la forme spécialisée et plus riche à laquelle on se
réfère dans des expressions comme « langage orienté-objets » ou « programmation à objets ».
c Jean-Paul R IGAULT
V 2.1 – 7 février 2010
GLOSSAIRE 201
pointeur
Un pointeur est un objet dont la valeur est l’adresse d’un autre objet. En
C, les pointeurs ont un type différent selon le type de l’objet pointé.
Les pointeurs jouent un rôle central en C, mais ils constituent aussi un
danger potentiel pour la sécurité de programmation.
préprocesseur
Avant la compilation, un fichier-source de C est analysé par le prépro-
cesseur (cpp) qui effectue un certain nombre de remplacement textuels
(c’est-à-dire lexicaux).
Le préprocesseur est étudié au chapitre 6. Profondément remodelé lors de
la normalisation, il constitue une des causes d’incompatibilité principales
entre ANSI C et le C traditionnel.
procédure
En C, il s’agit simplement d’une fonction qui n’a pas de valeur de retour
(on déclare ce type void).
programmation (procédurale, structurée, impérative)
C’est le type de programmation habituelle avec les langages comme FOR-
TRAN, Pascal, Ada, et bien sûr C. Le flot de contrôle y est séquentiel sauf
si l’on utilise des instructions de contrôle (sélection, boucles, goto...); les
procédures et fonctions permettent de structurer le flot de contrôle.
La programmation structurée est en fait un ensemble de règles ou de
conseils pour structurer le flot de contrôle. L’éradication du goto en est
son trait le plus connu.
prototype de fonction
Voir signature d’une fonction et type.
référence
Un référence est la désignation d’un objet. La forme la plus simple de réfé-
rence est donc le nom d’un objet (un identificateur). Mais C permet bien
d’autres formes qui seront étudiées au fur et à mesure de leur introduc-
tion.
Une référence ne doit pas être confondue avec la valeur de l’objet réfé-
rencé.
Dans le jargon de C, on designe une référence par le terme lvalue qui rap-
pelle qu’une référence est requise en partie gauche d’un affectation (lvalue
comme abréviation de left value); la valeur de l’objet est alors désignée,
pour la même raison, par le vocable rvalue (comme right value).
L’objet référencé peut être une variable ou une constante (dans le second
cas, la lvalue ne peut pas apparaitre en partie gauche d’une affectation!).
Bien sûr une référence rencontrée en partie droite d’un affectation est
« déréférencée » pour rendre la valeur de l’objet. Ainsi dans l’affectation
suivant entre deux entiers (type int)
int i, j;
...
i = j;
i est une référence (lvalue) alors que j est une valeur (rvalue).
référence externe
En cas de compilation séparée, une référence externe est une référence
dans un fichier, disons fic.c, à un objet possiblement défini dans un
autre fichier de compilation. Si l’objet est effectivement défini ailleurs,
alors le fichier-objet fic.o contiendra une référence externe non résolue.
référence (externe) non résolue
Voir référence externe et édition de liens.
sémantique d’exécution
La signification du programme, ce qu’il est censé faire, la fonction qu’il
calcule, la transformation qu’il effectue sur ses données d’entrée pour
produire les résultats en sortie, etc...
sémantique statique
Voir analyse statique.
signature d’une fonction
C’est le type d’une fonction qui est défini par
– le type de la valeur de retour (c’est-à-dire du résultat) de la fonction,
– le nombre et le type de chacun des arguments.
En ANSI C, la signature d’une fonction est déclarée grâce au prototype
(qui précise en outre le nom de la fonction).
spécification d’un module
La partie d’un module qui contient les déclarations des objets exportés
par le module (leur définitions sont dans le corps du module).
Si un autre module veut utiliser ces objets, il doit rendre visible (impor-
ter) la spécification. En C, ceci est réalisé par le directive #include du
préprocesseur.
source
Voir fichier-source.
statique
Voir dynamique.
typage fort
Voir type.
type
Un type définit un ensemble d’objets partageant un même ensemble
d’opérations. Par exemple, l’ensemble des entiers avec les opérations
usuelles (arithmétiques, logiques...).
On distingue en C les types de base (entiers et réels) et les types utilisateur
qui sont obtenus par des opérations de construction de type. Ces types
utilisateur sont des agrégats (tableaux, structures, unions).
En ANSI C, tout objet a un type qui doit être connu avant l’utilisation
de l’objet. Le compilateur vérifie (donc de manière statique) le bon usage
des types (typage fort).
valeur (d’un objet)
Voir objet.
variable
Un objet modifiable, le contraire d’une constante.
c Jean-Paul R IGAULT
V 2.1 – 7 février 2010
Index
– Symboles – <stdbool.h>, 58
=, 28, 63
, 71 -=, 64, 107
., 94, 97, 110 ==, 28, 58, 106
..., 144 >, 28, 58, 106
-, 32, 57, 64, 107 ->, 94, 97, 110
— unaire, 57 >=, 28, 58, 106
∼, 60 >>=, 64
#, 130, 134 [], 119
##, 134 __DATE__, 135
#define, 84, 131–135, 155 __FILE__, 135
#elif, 136 __LINE__, 135
#else, 136 __STDC__, 135
#endif, 136 __STDC_VERSION__, 135
#error, 138 __TIME__, 135
#if, 136 {}, 26
#ifdef, 136
#ifndef, 137
#define, 26, 130, 131, 157, 202 –A–
#line, 138
#pragma, 138 abort, 172
#undef, 135, 155 accolade, 26
%, 33, 57 Ada, 18
%=, 64 addition, 57
& — de pointeurs, 108
— unaire, 104 — d’un entier à un pointeur, 107
* , 32, 57, 119 adresse
— unaire, 105 — d’un objet, 31
*=, 64 opérateur —, 104
+, 32, 57, 107 affectation, 195
— unaire, 57 — composée, 63
++, 37, 64, 107 — de tableau, 85
+=, 64, 107 — simple, 63
,, 66, 68 conversion lors d’une —, 70
,=<=, 106 expression d’—, 28
/, 32, 57 agrégat, 50, 83, 195, 202
/=, 64 initialisation d’—, 85, 86, 91, 96
1 alignement, 93
complément à —, 60 allocation
2 — dynamique, 113, 158, 161
complément à —, 69 alternative, 32
;, 27, 71 analyse
<, 28, 58, 106 — lexicale, 195
<<=, 64 — statique, 195
<=, 28, 58 — syntaxique, 195
<assert.h>, 61 and, 176
c Jean-Paul R IGAULT
V 2.1 – 7 février 2010
INDEX 205
c Jean-Paul R IGAULT
V 2.1 – 7 février 2010
INDEX 207
–K– –M–
kate, 178 machine
kde, 184 langage- —, 200
kdevelop, 184 MacOs X, 20
Kernighan, Brian W., 17 macro, 58, 61, 84, 131–135
Korn, David, 197 définition de —, 131
ksh, 20 définition récursive de —, 134
variadic —, 132
main, 26, 151, 172
–L– arguments de —, 39
majuscule
L, 52 — et minuscule, 43
l, 52 make, 21, 27, 115, 178, 179, 183, 185
LANG, 20 Makefile, 21
langage-machine, 200 malloc, 113, 125, 158, 161, 172, 180
ld, 196, 199 <math.h>, 173
lexical mélange
éément —, 44 — de types, 70
lexicale memcmp, 174
analyse —, 195 memcpy, 85, 174
constante —, 198 mémoire
library, 200 — partagée, 54
standard —, 19 fuite de —, 180
liens taille —, 65
éditeur de —, 158 Microsoft Windows, 20
édition de —, 196, 198 minuscule
ligne majuscule et —, 43
fonction en —, 132, 146 modifieur
ligne-suite, 129 — de type, 53
<limits.h>, 52 modulaire
Linux, 1 programmation —, 164
liste, 111 modularité, 18, 200
— variable d’arguments, 144 module, 158, 164, 200
littérale corps d’un —, 164, 198
chaîne de caractères —, 26, 48, 120, interface d’un —, 164
143 spécification d’un —, 202
constante —, 46–50, 198 modulo, 57
local mono-dimensionnel
fonction —, 145 tableau—, 83
objet —, 159 multi-dimensionnel
locale tableau—, 86
variable —, 71, 88, 92 multi-octet
<locale.h>, 174, 175 caractère —, 43
logique multiple
— et, 60 pointeur —, 105
— opérateur, 58 multiplication, 57
— ou, 60
et —, 38
ou —, 38 –N–
c Jean-Paul R IGAULT
V 2.1 – 7 février 2010
INDEX 209
c Jean-Paul R IGAULT
V 2.1 – 7 février 2010
INDEX 211
–V– –Z–
valeur, 202 zsh, 20
— de vérité, 72
— de vérité d’une expression, 58
passage par —, 142
valgrind, 2
variable, 202
— d’environnement, 21
— locale, 71, 88, 92
liste — d’arguments, 144
c Jean-Paul R IGAULT
V 2.1 – 7 février 2010