Vous êtes sur la page 1sur 123

Avant- propos

Avant-propos
Nous vous proposons, par ce livre, d'apprendre la programmation en langage C. Dans ce
livre, nous supposerons que le lecteur a des notions de base en programmation,
algorithmique et structures de donnes; si vous n'en avez pas, ou si vous voulez rviser des
notions pendant l'apprentissage du C, vous pouvez consulter les wikilivres associs:
Programmation, Algorithmique et Structures de donnes.
Le langage C permet d'apprhender des principes informatiques de bas niveau, lis
l'architecture d'un ordinateur, comme la gestion de la mmoire. Si la connaissance de ces
principes peut aider comprendre certains cts du langage C, ils ne sont cependant pas
ncessaires pour lire ce wikilivre, les concepts tant expliqus chaque fois que
ncessaire.
Ce livre, en constante volution, est issu d'un projet collaboratif. Si vous trouvez des
erreurs, vous pouvez le corriger directement l'aide des liens Modifier sur chaque
page. Vous pouvez poser des questions et apporter vos remarques en laissant un nouveau
message [1] sur la page de discussion de ce livre. Toute question ou participation est la
bienvenue !
Bonne lecture !

Rfrences
[1] http:/ / fr. wikibooks. org/ w/ wiki. phtml?title=Discuter:Programmation_C& amp;action=edit&
amp;section=new

Sommaire
Voici une description rapide des diffrents chapitres qui constituent ce livre:
Introduction
Le C a une longue histoire... Cr il y a plus de 30 ans, il a beaucoup volu depuis.
Nous expliquerons ici son histoire, dcrirons ses principales caractristiques et
domaines d'utilisation, et fournirons des raisons de l'apprendre (ou de ne pas
l'apprendre).
Bases du langage
Dans cette partie, nous tudierons les concepts de base du langage C, sa syntaxe ainsi
que la manire dont un programme est structur.
Types de base
Ensuite, nous prsenterons les diffrents types de base que fournit le langage C. Ces
types tant pour l'essentiel des nombres (entiers, rels et complexes), ce chapitre
traitera aussi de l'arithmtique associe ces types. Les types caractres, qui sont la
base de nombreux traitements, et ont un rle particulier dans ce langage, seront aussi
dfinis.
Classe de stockage
Le C permet de construire des constantes, par le biais de classes de stockages.
Celles-ci permettent de dfinir plus gnralement des types drivs des types de base

Sommaire
et seront tudis dans ce chapitre.
Oprateurs
Nous tudierons ensuite les oprateurs permettant de travailler sur les types de base,
ainsi que les rgles gnrales qui structurent les expressions en C.
Structures de contrle - tests
La notion d'expression dfinie, nous aborderons les moyens de contrler le flux
d'excution d'un programme. Tout d'abord, les tests de type Si...Alors...Fin Si seront
tudis.
Structures de contrle - itrations
Dans la suite du chapitre prcdent, nous tudierons les structure d'itrations, ou
boucles, de type Pour, Tant que ou drivs.
Fonctions et procdures
Comment le C prend en charge les fonctions et procdures.
Tableaux
Comment le C gre les tables de donnes.
Pointeurs
Les pointeurs sont une spcificit du C, souvent mal comprise. Ce chapitre dfinira et
montrera comment bien utiliser les pointeurs, ainsi que les erreurs viter dans leur
manipulation.
Types avancs - structures, unions, numrations
Explications sur les types avancs du C.
Prprocesseur
Le prprocesseur est un langage au-dessus du C, que l'utilisateur rencontre pour la
premire fois dans l'utilisation des directives d'inclusion d'en-ttes de la bibliothque
standard. Cependant, il est capable de bien plus, et fournit des outils comme les
macros ou l'inclusion conditionnnelle, qui sont l'objet de ce chapitre.
Bibliothque standard
Cette partie a pour but de familiariser le lecteur avec la bibliothque standard du
C. Celle-ci propose un ensemble de fonctions, mais aussi de types ou de variables
permettant d'effectuer des oprations spcifiques, comme les traitements de fichiers,
les calculs mathmatiques, ou la gestion des chanes des caractres.
Chanes de caractres
Ce chapitre traite de toutes les fonctions permettant d'effectuer des traitements sur
les chanes de caractres : concatnation, copie, recherche de caractres, etc.
Entres/sorties
Ce chapitre traite des interactions avec l'utilisateur.
Erreurs
La bibliothque standard utilise un mcanisme unique pour la gestion d'erreur, qui
permet un programme de dterminer prcisment la cause de l'chec d'une fonction
de la bibliothque standard.
Mathmatiques
Ce chapitre dtaille les fonctions mathmatiques fournies par la bibliothque standard.

Sommaire
Gestion de la mmoire
La bibliothque standard fournit un moyen d'allouer et de librer dynamiquement de la
mmoire au cours de l'excution du programme. Cette fonctionnalit est surtout
utilise pour rsoudre des problmes dont le volume des donnes traiter varie d'une
excution l'autre.
Gestion des signaux
Un signal est une interruption logicielle reue par un processus. Ces interruptions
fournissent un moyen de communication entre le systme et un programme, pour
informer le programme de situations d'erreur (utilisation d'une zone de mmoire
invalide, par exemple), ou informer un processus d'vnements asynchrones (fin d'une
opration d'entre/sortie). Le C permet d'envoyer et de traiter la rception de ces
signaux.
Conclusion

Introduction
Historique
Naissance
Le langage C a t invent aux Bells Labs en 1972 par Dennis Ritchie pour permettre
l'criture du systme d'exploitation UNIX, alors dvelopp par Ken Thompson et Dennis
Ritchie.
Le systme d'exploitation UNIX, n la fin des annes 1960 - dbut des annes 1970, a t
crit directement en langage assembleur pour les machines auxquelles il tait destin. Si le
langage assembleur permettait d'crire un tel systme, il n'en tait pas moins peu ais
utiliser. Un tel langage est en effet particulier un type de processeur, ce qui fait que tout
le systme devait tre rcrit pour le faire fonctionner sur une nouvelle architecture. Cela
fait que son principal crateur, Ken Thompson, souhaita rapidement utiliser un langage
plus volu pour rcrire UNIX.
Parmi les langages disponibles l'poque, BCPL (pour Basic Combined Programming
Language, qui est une simplification de CPL), cr par Martin Richards en 1966, tait
intressant. Sans entrer dans des descriptions dtailles, BCPL est un langage simple,
procdural, et non typ. Sa simplicit permettait de crer facilement des compilateurs
BCPL sur les machines de l'poque, o les ressources taient trs limites (le premier
ordinateur utilis par Keith Thompson pour lancer Unix tait un PDP-7, qui disposait d'une
mmoire de 4000 mots de 18 bits, soit moins de 9 Ko). Ken Thompson l'a fait voluer pour
concevoir le langage B, qu'il a implment sur les premires machines UNIX. Cependant,
certaines limitations du langage B ont fait qu'UNIX n'a pu tre rcrit dans ce langage.
partir de 1971, Dennis Ritchie fit voluer B, pour rpondre ces problmes. l'image des
programmeurs qui incrmentent les versions de leurs programmes, Ritchie incrmenta
la lettre B pour appeler le nouveau langage C. Cette volution se stabilisa vers 1973,
anne partir de laquelle UNIX et les utilitaires systmes d'UNIX ont t rcrits avec
succs en C.

Introduction

Dveloppement
Par la suite en 1978, Brian W. Kernighan documenta trs activement le langage, pour
finalement publier avec Ritchie le livre de rfrence The C Programming Language. On
appelle souvent C K&R le langage tel que spcifi dans la premire dition de ce livre.
Dans les annes qui suivirent, le langage C fut port sur de nombreuses autres machines.
Ces portages ont souvent t faits, au dbut, partir du compilateur pcc de Steve Johnson,
mais par la suite des compilateurs originaux furent dvelopps indpendamment. Durant
ces annes, chaque compilateur C fut crit en suivant les spcifications du K&R, mais
certains ajoutaient des extensions, comme des types de donnes ou des fonctions
supplmentaires, ou interprtaient diffremment certaines parties du livre (qui n'tait pas
forcment trs prcis). cause de cela, il fut de moins en moins facile d'crire des
programmes en C qui puissent fonctionner tels quels sur un grand nombre d'architectures.

Normalisation
Pour rsoudre ce problme, et ajouter au C des possibilits dont le besoin se faisait sentir
(et dj existantes dans certains compilateurs), en 1989, l'organisme national de
normalisation des USA (ANSI) normalisa le C. Le nom exact de cette norme est ANSI
X3.159-1989 Programming Language C, mais elle fut (et est toujours) connue sous les
dnominations ANSI C ou C89. Puis l'ANSI soumit cette norme l'Organisation
internationale de normalisation (ou ISO), qui l'accepta telle quelle et la publia l'anne
suivante sous le numro ISO/IEC 9899:1990 (le document tant connu sous le nom ISO
C90, ou C90). Le besoin de normalisation de l'poque tant extrmement pressant,
pratiquement tous les diteurs de compilateurs C de l'poque se sont mis modifier leurs
compilateurs pour supporter la norme (et mme n'ont pas attendu la publication officielle
pour lancer ces modifications), et de fait la quasi totalit des implmentations existant ce
jour comprennent ces normes. L'ISO publia dans les annes qui suivirent deux ensembles
de correctifs, et un amendement pour la gestion des caractres internationaux.
tant donne la grande diversit des implmentations existants au moment de l'laboration
de la norme, et le principe de ngociation qui base les processus de normalisation, la norme
C est un compromis. Son but tait principalement double : d'une part assurer le plus
possible la portabilit du code C, pour simplifier le portage de programmes C d'une
implmentation une autre (ce qui tait souvent un cauchemar raliser), d'autre part
donner une certaine libert aux diteurs pour proposer des extensions spcifiques. Pour ce
faire, la norme a dfini des niveaux de conformit pour les programmes C et les
implmentations, allant du programme strictement conforme, que toute implmentation
doit accepter, et qui fonctionnera exactement de la mme manire, aux programmes
dpendant d'extensions.
On peut noter que le livre de Kernighan et Ritchie a t republi dans une seconde dition,
pour reflter les changements du C89.
Enfin, en 1999, l'organisme ISO proposa une nouvelle version de la norme, qui reprenait
quelques bonnes ides du langage C++ (voir plus bas). Il ajouta aussi le type long long
d'une taille minimale de 64 bits, les types complexes, l'initialisation des structures avec des
champs nomms, parmi les modifications les plus visibles. Le nouveau document, qui au
niveau de l'ISO est celui ayant autorit aujourd'hui, est ISO/IEC 9899:1999, connu sous le
sigle C99. Deux ensembles de correctifs cette version ont t publis jusqu' prsent. Au
sens strict, la dnomination ISO C correspond donc actuellement la norme de 1999

Introduction
corrige, mais l'usage est tel qu'il est prfrable de toujours prciser de quelle version on
parle (89/90 ou 99), et c'est ce qui sera fait dans cet ouvrage lorsque la distinction sera
ncessaire. Il est noter que, si la norme C90 a t largement adopte par les diteurs de
compilateurs C, trs peu implmentent la version C99. En effet, le besoin des utilisateurs
s'est fait moins pressant, et l'effort ncessaire pour rendre les compilateurs conformes a
rarement t men terme.
L'activit du groupe de travail de la norme C est, depuis, porte plus sur la maintenance
du langage qu' de vritables volutions . La prochaine version, dnomme
officieusement C1X pour l'instant, n'est pas encore publie et ne sera pas tudie dans ce
livre[1] .
La norme C spcifie la syntaxe du langage ainsi qu'une bibliothque de fonctions simple.
Cette bibliothque est moins fournie que dans d'autres langages, mais cela est d au
domaine d'utilisation trs vari de ce langage. En particulier, pour assurer une portabilit
maximale du langage entre les implmentations embarques, les ordinateurs de type PC et
les supercalculateurs (par exemple), certaines fonctions n'ont pas t acceptes dans la
norme. On peut citer, entre autres, la programmation parallle, les communications entre
processus, la communication rseau ou les interfaces graphiques...
Cela fait qu'en parallle de cette normalisation du langage C, certaines extensions ont elles
aussi t standardises, voire normalises. Ainsi, par exemple, des fonctions spcifiques
aux systmes UNIX, sur lesquels ce langage est toujours trs populaire, et qui n'ont pas t
intgres dans la norme du langage C, ont servi dfinir une partie de la norme POSIX.
Dans ce livre, nous tudierons surtout le langage C tel que dfini dans les normes ISO
cites ci-dessus. La norme C99 n'tant pas implmente par tous les compilateurs, et
beaucoup de programmes existants tant dvelopps en C90, les diffrences entre les deux
normes seront indiques chaque fois que ncessaire. Toutefois, nous illustrerons certains
chapitres avec des exemples d'extensions courantes qu'un dveloppeur C peut rencontrer,
en particulier concernant les fonctions qui ne sont pas fournies par la norme C elle-mme.
Mais ces exemples d'extensions seront mineurs. D'autres Wikilivres tudient plus en dtails
de telles extensions, vous pouvez les trouver en regardant les livres lists sur la page
Catgorie:C.

Norme et documents de rfrence


La norme qui dfinit le langage C est labore au niveau international par un groupe de
travail (working group) de l'ISO. Ce groupe de travail fait partie plus prcisment du comit
technique commun l'ISO et la Commission Electrotechnique Internationale, le Joint
Technical Commitee 1, qui labore les normes internationales concernant les technologies
de l'information. Il se dcompose en sous-comits, donc le SC22, responsable des langages
informatiques. Le SC22 rassemble plusieurs groupes de travail, chacun portant sur un
langage spcifique, ou sur des considrations indpendantes des langages. On peut ainsi
citer, par exemple :
JTC1/SC22/WG9 pour Ada ;
JTC1/SC22/WG14 pour le C ;
JTC1/SC22/WG21 pour le C++.
Comme pour toute norme internationale, c'est l'ISO qui dcide des modalits de sa
distribution. Comme la grande majorit des normes ISO, celle-ci n'est pas en libre
distribution, mais en vente[2] . Le lecteur dsirant acheter un exemplaire de la norme peut

Introduction
le faire sur le site officiel de l'ISO, par exemple.
Les corrections, ou questions, concernant la norme sont diffuses individuellement par le
WG14 sur leur site sous le nom de Defect Report (DR), et rassembles pour publication
offcielle dans des rectificatifs techniques ou Technical Corrigenda (TC). Deux ont t
publis pour le C99, en 2001 et 2004.
Par ailleurs, les brouillons (ou drafts) de la norme appartiennent au groupe de travail, qui
peut dcider de les publier. C'est le cas des documents n869, qui est le dernier brouillon
disponible avant la publication de C99, et n1124, correspondant au C99 auquel ont t
ajouts les TC1 et TC2 (le TC3 ayant t publi en 2007).
On pourra noter que, tout comme l'ANSI l'a fait pour le C89, le WG14 a publi un Rationale,
qui est un commentaire de la norme C99. Ces deux textes (en anglais, toujours) peuvent
tre intressants pour comprendre les motivations de certains choix dans l'volution du
langage.
Tous ces documents sont disponibles sur le site officiel du groupe de travail (les URLs et
rfrences se trouvent dans la bibliographie la fin de ce livre).
Toutefois, il faut bien noter qu'une norme est un texte trs technique, qui sert de rfrence,
et est trs loin d'tre un texte pdagogique. Un dbutant fera mieux de lire un ouvrage
comme ce wikilivre pour apprendre le C que lire directement la norme, au risque d'tre
rapidement dcourag. Le prsent ouvrage est construit de manire ne pas avoir
recourir au texte de la norme, et n'y fera rfrence que rarement, mais le lecteur intress
et averti pourra y vrifier des points complexes qui ne seront pas dtaills ici.

C et C++
Ce livre ne porte pas sur le C++, mais ces deux langages tant d'apparence trs proches. Il
convient, pour viter la confusion au lecteur dsireux d'apprendre l'un ou l'autre, de
prciser qu'ils sont trs diffrents et que, si la connaissance de l'un peut aider
l'apprentissage de l'autre, ils ne doivent pas tre confondus. L'ide du langage qui est
devenu C++ a t lance par Bjarne Stroustrup en 1979, dans un souci de faire voluer le
C de manire le rendre plus robuste . L'apport le plus visible de cette volution est que
le C++ est orient objet, alors que le C est procdural, mais ce n'est pas le seul.
Le langage C++ s'est cr partir du C puis, rapidement, en parallle lui, et en 1998 est
devenu une norme ISO au mme titre que le C. Les groupes de travail respectifs du C et du
C++ communiquent rgulirement dans le but d'assurer une certaine compatibilit entre
les deux langages. De fait, un certain nombre d'ides issues des rflexions autour du C++
se sont vues intgres dans la norme C99[3] . Cependant, la croyance populaire selon
laquelle le C est un sous-ensemble du C++, ou qu'utiliser un compilateur C++ pour un
programme C ne pose pas de problme, est fausse, et plus d'un titre :
un source strictement conforme du point de vue du C peut tre invalide au sens du
C++[4] ;
un source qui est la fois strictement conforme pour le C et le C++ peut avoir un
comportement diffrent dans ces deux langages[5] .
Durant l'apprentissage du C, le lecteur devra donc faire attention. Un certain nombre
d'diteurs, mettant profit la grande proximit des deux langages, distribuent ensemble un
compilateur C et un compilateur C++, en appelant parfois l'ensemble C/C++, ce qui
entrane une grande confusion chez les utilisateurs, qui ont parfois du mal savoir quel est

Introduction
le langage dans lequel ils travaillent. L'utilisateur doit donc apprendre comment fonctionne
son implmentation, pour dterminer quel(s) langage(s), norme(s) et extension(s) elle
supporte, et comment le faire fonctionner dans l'un ou l'autre mode.
Une liste de compilateurs, avec les supports des normes, est rfrence en Bibliographie.

Pourquoi apprendre le langage C ?


Avec son approche bas-niveau, le C permet d'obtenir des programmes trs optimiss,
pratiquement autant que s'ils avaient t crits directement en assembleur. Avec un peu
d'effort, il est mme possible d'utiliser une approche oriente objet, au prix d'une certaine
rigueur que le langage et les compilateurs, dans une certaine mesure, sont trs loin
d'imposer.
Les systmes d'exploitation pour ordinateur de bureau les plus rpandus actuellement sont
Windows de Microsoft, Mac OS X d'Apple, et GNU/Linux. Ils sont tous trois crits en
langage C. Pourquoi ? Parce que les systmes d'exploitation tournent directement au
dessus du matriel de la machine. Il n'y a pas de couche plus basse pour grer leurs
requtes. l'origine, les systmes d'exploitation taient crits en assembleur, ce qui les
rendait rapides et performants. Toutefois, crire un OS en assembleur est une tche
pnible et cela produit du code qui ne peut s'excuter que sur une seule architecture
processeur, comme l'Intel X86 ou l'AMD 64. crire un OS dans un langage de plus haut
niveau, comme le langage C, permet au programmeur de porter son systme d'exploitation
sur une autre architecture sans avoir tout rcrire.
Mais pourquoi utiliser le C et non
Java,
Basic
ou
Perl
?
Principalement cause de la
gestion de la mmoire. la
diffrence de la plupart des autres
langages de programmation, le
langage
C
permet
au
programmeur de grer la mmoire
de la manire qu'il aurait choisie
s'il avait utilis l'assembleur. Les
langages comme le Java et le Perl
permettent au programmeur de ne
pas avoir se soucier de
Kenneth Thompson ( gauche) et Dennis Ritchie ( droite), les
l'allocation de la mmoire et des
crateurs du langage C
pointeurs. C'est en gnral un
point positif, car il est assez pnible et inutile de devoir se soucier de la gestion de la
mmoire lorsqu'on crit un programme de haut niveau comme un rapport sur les rsultats
trimestriels.
Cependant lorsqu'on parle d'crire un programme de bas niveau comme la partie du
systme d'exploitation qui s'occupe d'envoyer la chane d'octets correspondant notre
rapport trimestriel depuis la mmoire de l'ordinateur vers le buffer de la carte rseau afin
de l'envoyer vers une imprimante rseau, avoir un accs direct la mmoire est
fondamental ce qui est impossible faire dans un langage comme Java par exemple. Les
compilateurs C produisent de plus trs souvent un code rapide et performant.

Introduction
Est-ce vraiment si merveilleux que le langage C soit un langage si rpandu ? Par effet
domino, la gnration suivante de programmes suit la tendance de ses anctres. Les
systmes d'exploitation crits en C ont toujours des librairies crites en C. Ces
bibliothques systme sont leur tour utilises pour crire des bibliothques de plus haut
niveau (comme OpenGL ou GTK) et le programmeur de ces bibliothques dcide souvent
d'utiliser le mme langage que celui utilis par ces bibliothques systme. Les
dveloppeurs d'applications utilisent ces bibliothques de haut niveau pour crire des
traitements de texte, des jeux, les lecteurs multimdia, etc... La plupart d'entre eux
choisiront d'utiliser pour leur programme le mme langage que les bibliothques de haut
niveau. Et le schma se reproduit l'infini...

Rfrences
[1] Le lecteur intress peut rcuprer la dernire version de travail sur la page "projects" du site du WG.
[2] Certaines normes sont rendues disponibles gratuitement et en libre tlchargement sur internet, mais leur
nombre est relativement faible par rapport l'ensemble des normes existantes.
[3] On peut citer comme exemple les commentaires de fin de ligne, les macros nombre variable d'arguments, les
dclarations de symboles n'importe quel endroit et le mot cl inline pour pallier la prcarit des macros
[4] L'exemple le plus simple serait la dfinition d'une variable ayant pour identifieur un mot rserv du C++,
comme int class;.
[5] Le lecteur curieux pourra se rfrer cet article (http:/ / david. tribble. com/ text/ cdiffs. htm) de David R.
Tribble, en anglais, qui numre les diffrences entre C99 et C++98.

Bases du langage
Bonjour!
L'un des plus petits programmes possible en langage C est :
#include <stdio.h>
int main(void)
{
printf("Bonjour !\n");
return 0;
}
(Les numros de lignes ont t ajouts pour la lisibilit, mais ne font pas partie du
programme.)
Ce programme a pour seule fonction d'afficher le texte Bonjour ! suivi d'un retour la
ligne. Voici la description de chaque ligne qui le compose :
#include <stdio.h>
Inclusion de l'en-tte nomm <stdio.h>. Il est li la gestion des entres et sorties
(STanDard Input Output, entres/sorties standards) et contient, entre autres, la dclaration
de la fonction printf permettant d'afficher du texte format.
int main(void)

Bases du langage
Dfinition de la fonction principale du programme, main (principal en anglais). Il s'agit de
la fonction appele par le systme d'exploitation au dmarrage du programme, tout
programme en C doit donc la dfinir. La partie entre parenthses spcifie les paramtres
que reoit le programme: ici, le programme n'utilisera aucun paramtre, ce qui est spcifi
en C avec le mot-cl void (vide). La partie gauche du nom de la fonction main spcifie le
type renvoy par la fonction: la fonction main est dfinie par la norme C comme renvoyant
une valeur entire, de type int (pour integer, entier), au systme d'exploitation.
{

Dbut de la dfinition de la fonction main.


printf("Bonjour !\n");
Invocation de la fonction printf (print formatted, affichage format. La notion de format
sera explique dans le chapitre ddi cette fonction.). La chane Bonjour ! va tre
affiche, elle sera suivie d'un retour la ligne (il est reprsent par \n).
return 0;
La fonction main a t dfinie comme retournant (to return) une valeur de type int, on
renvoie donc une telle valeur. Par convention, la valeur 0 indique au systme d'exploitation
que le programme s'est termin normalement.
}

Fin de la dfinition de la fonction main.


Ce premier exemple ne dtaille videmment pas tout ce qui concerne la fonction main, ou
printf, par exemple. La suite de l'ouvrage prcisera les points ncessaires dans les
chapitres appropris (voir par exemple le paragraphe la fonction main du chapitre
Fonctions et procdures, ou le paragraphe Sorties formates du chapitre Entres/sorties).

Compilation
Pour excuter un programme C, il faut pralablement le compiler?.
Tapez le code source du programme Bonjour ! dans un diteur de texte? et sauvegardez-le
sous le nom bonjour.c (l'extension .c n'est pas obligatoire, mais est usuelle pour un
fichier source en C). Ouvrez une fentre de commandes (terminal sous Unix, commandes
MS-DOS sous Windows), et placez-vous dans le rpertoire o est sauvegard votre fichier.
Pour compiler avec le compilateur GNU CC, il faut taper la commande :
cc bonjour.c
D'autres compilateurs pour le langage C peuvent tre utiliss. La compilation du
programme produira un fichier excutable (a.out sous Unix, a.exe sous Windows) qui
peut tre excut (en tapant ./a.out sous Unix, a sous Windows).

Bases du langage

10

lments de syntaxe
Identificateurs
Les identificateurs commencent par une lettre ou le caractre soulign ("_") et peuvent
contenir des lettres, des chiffres et le caractre soulign (cependant, les identificateurs
commenant par deux caractres souligns, ou un caractre soulign suivi d'une majuscule
sont rservs au compilateur, et ne doivent pas tre utiliss dans un programme
ordinaire). Tous les mots-cls ainsi que les symboles (variables, fonctions, champs, etc.)
sont sensibles la casse des lettres. Quelques exemples d'identificateurs valides :
toto
_coin_coin76

Mots rservs du langage


Le langage, dans la norme C90, possde 32 mots rservs (ou mots-cls) qui ne peuvent pas
tre utiliss comme identificateurs. La norme C99 y a ajout 5 mots-cls. Le tableau
ci-dessous les liste tous, avec la mention C99 pour indiquer ceux dfinis uniquement dans
la dernire version:
auto

break

case

char

const

continue

default

do

double

else

enum

extern

float

for

goto

if

inline (C99)

int

long

register

restrict (C99)

return

short

signed

sizeof

static

struct

switch

typedef

union

unsigned

void

volatile

while

_Bool (C99)

_Complex
(C99)

_Imaginary (C99)

Un programme C90 peut dfinir inline comme un identifiant de variable ou de fonction,


par exemple, mais cela est trs dconseill, car un tel programme sera invalide au sens de
la norme C99. Une mise jour du compilateur peut alors faire refuser un tel programme,
alors qu'il l'aurait accept avant. Pour viter de telles situations, il faut considrer tous les
mots-cls du tableau prcdent comme rservs, quel que soit le compilateur ou la norme
utiliss.

Commentaires
Les commentaires commencent par /* et se terminent par */, ils ne peuvent pas tre
imbriques :
/* ceci est un commentaire */
/*
ceci est aussi un commentaire
*/
/* /* ceci est encore un commentaire */

Bases du langage
Ce code contient une erreur volontaire !
/* /* */ ceci n<font name="DejaVuSans"></font>est pas un commentaire */
Inclure des commentaires pertinents dans un programme est un art subtil. Cela ncessite
un peu de pratique pour savoir guider les lecteurs et attirer leur attention sur certaines
parties dlicates du code. Pour faire court, on ne saurait trop que rappeler ce clbre vers
de Nicolas Boileau, qui disait que ce que l'on conoit bien, s'nonce clairement et les mots
pour le dire arrivent aisment (Art Potique, 1674). Ou encore, pour adopter une
approche plus pragmatique : si un programmeur a du mal commenter (expliquer,
spcifier) le fonctionnement d'un passage de code, c'est que le code est mal conu et qu'il
serait bnfique de le rcrire plus lisiblement.
Attention! Les commentaires ne devraient pas tre employs pour dsactiver certaines
parties du code. Cette technique dissuade un programmeur d'en inclure puisque le langage
C ne permet pas d'imbriquer les commentaires. Pour cela, le prprocesseur dispose
d'instructions ddies, qui permettent heureusement de dsactiver du code en laissant les
commentaires.
Le // ajoute la possiblit de placer un commentaire d'une seule ligne, partir du //
jusqu' la fin de la ligne :
// commentaire
instruction; // autre commentaire
Cependant, ces commentaires la C++ sont apparus dans C99, et ne sont pas permis
en C90.

Instructions
Les instructions se terminent par un point-virgule (;), on peut placer autant d'instructions
que l'on veut sur une mme ligne (mme si ce n'est pas conseill pour la lisibilit du code).
Les blocs d'instructions commencent par une accolade ouvrante ({) et se terminent par une
accolade fermante (}). Les instructions doivent obligatoirement tre dclares dans
une fonction : il est impossible d'appeler une fonction pour initialiser une variable globale
par exemple (contrairement au C++).
/* une instruction */
i = 1;
/* plusieurs instructions sur la mme ligne */
i = 1; j = 2; printf("bonjour\n");
/* un bloc */
{
int i;
i = 5;
}
/* l'instruction vide */
;

11

Bases du langage
Dans la mesure o le compilateur ne se soucie pas des blancs (espaces et retours la
ligne), vous pouvez formater votre code comme vous l'entendez. Il y a beaucoup de religion
concernant les styles d'indentation, mais pour faire court et viter les guerres saintes, on
ne saurait trop conseiller que d'utiliser le mme nombre de blancs par niveau d'imbrication
de bloc.
noter que l'instruction vide tant valide en C, on peut donc pratiquement rajouter autant
de point-virgules que l'on veut. Le point-virgule ne sert pas uniquement marquer la fin
des instructions, les compilateurs l'utilisent gnralement comme caractre de
synchronisation, suite une erreur dans un programme source. En fait, en gnral,
lorsqu'une erreur est dtecte, les compilateurs ignorent tout jusqu'au prochain
point-virgule. Ce qui peut avoir des consquences assez dramatiques, comme dans
l'exemple suivant :
Ce code contient une erreur volontaire !
int traite_arguments( int nb, char * argv[] )
{
/* ... */
return 0
}
int main( int nb, char * argv[] )
{
int retour;
retour = traite_arguments( nb, argv );
/* ... */
}
On notera l'absence de point-virgule la fin de l'instruction return la fin de la fonction
traite_arguments. Ce que la plupart des compilateurs feront dans ce cas sera d'ignorer
tout jusqu'au prochain point-virgule. On se rend compte du problme : on a saut une
dclaration de fonction (avec ses deux paramtres) et une dclaration de variable. Ce qui
veut dire qu'une cascade d'erreurs va suivre suite l'oubli... d'un seul caractre (;) !

Dclarations de variables
T var1, var2, ..., varN;

Cette ligne dclare les variables var1, var2, ..., varN de type T. Une variable est dite locale,
si elle est dfinie l'intrieur d'une fonction et globale si dfinie en-dehors.
Par exemple:
int jour; /* 'jour' est une variable globale, de type entier */
int main(void)
{
double prix; /* 'prix' est une variable locale la fonction
'main', de type rel */
return 0;

12

Bases du langage
}
Variables locales
Les variables locales (aussi appeles automatiques) ne sont visibles que dans le bloc dans
lequel elles sont dfinies, et n'existent que durant ce bloc. Par exemple:
Ce code contient une erreur volontaire !
int fonction(int n)
{
int i; /* i est visible dans toute la fonction */
i = n + 1;
{ /* dbut d'un nouveau bloc */
int j; /* j est visible dans le nouveau bloc *seulement* */
j = 2 * i; /* i est accessible ici */
}
i = j; /* ERREUR : j n'est plus accessible */
return i;
}
Le code prcdent accde i depuis un bloc contenu dans le bloc o i a t dfini, ce qui
est normal. Puis il essaye d'accder la valeur de la variable j en-dehors du bloc o elle
t dfinie, ce qui est une erreur.
Les variables locales ne sont pas initialises automatiquement et contiennent
donc, aprs leur dclaration, une valeur alatoire. Utiliser une telle valeur peut causer
un comportement alatoire du programme. Le programme suivant essaye d'afficher la
valeur d'une telle variable, et on ne peut savoir l'avance ce qu'il va afficher. Si on le lance
plusieurs fois, il peut afficher plusieurs fois la mme valeur aussi bien qu'il peut afficher
une valeur diffrente chaque excution:
Ce code contient une erreur volontaire !
#include <stdio.h>
int main(void)
{
int n;
printf("La variable n vaut %d\n", n);
return 0;
}
Avant la normalisation ISO C99, les dclarations de variables locales devaient
obligatoirement tre places juste au dbut d'un bloc et s'arrtaient la premire
instruction rencontre. Si une dclaration est faite au-del, une erreur sera retourne.
Suivant la norme C99, les dclarations peuvent se trouver n'importe o dans un bloc. Par
exemple, le code suivant est correct suivant la norme C99, mais pas suivant la norme C90,
car la variable a est dfinie aprs l'instruction puts("Bonjour!");:
int ma_fonction(int n)
{
puts("Bonjour !");

13

Bases du langage
int a; /* Valide en C99, invalide en C90 */
/* autre chose... */
return a;
}
Variables globales
Une attention particulire doit tre porte aux variables globales, souvent source de
confusion et d'erreur. Utilisez des noms explicites, longs si besoin et limitez leur usage au
seul fichier o elles sont dclares, en les dclarants statiques.
Les variables globales sont initialises avant que la fonction main s'excute. Si le
programmeur ne fournit pas de valeur initiale explicitement, chaque variable reoit une
valeur par dfaut suivant son type:
un nombre (entier, rel ou complexe) est initialis 0;
les pointeurs sont initialiss NULL;
pour une structure, l'initialisation se fait rcursivement, chaque membre tant initalis
suivant les mmes rgles;
pour une union, le premier membre est initialis suivant ces rgles.
Pour initialiser explicitement une variable globale, on ne peut utiliser que des constantes;
en particulier, on ne peut appeler de fonction, contrairement d'autres langages.
Ce code contient une erreur volontaire !
int jour_courant(void)
{
/* retourne le n du jour courant */
}
int jour = jour_courant(); /* ERREUR ! */
int main(void)
{
/* ... */
return 0;
}
Le code prcdent ne pourra pas compiler, car on ne peut appeler directement
jour_courant() pour initialiser jour. Si vous avez besoin d'un tel comportement, une
solution est de faire l'initalisation explicite dans main:
int jour_courant(void)
{
/* retourne le n du jour courant */
}
int jour; /* 'jour' est initialis 0 *avant* le dbut de 'main' */
int main(void)
{
jour = jour_courant();

14

Bases du langage
/* ... */
return 0;
}

Traitements des ambiguts


Le C utilise un mcanisme lgant pour lever des constructions syntaxiques en apparence
ambigus. L'analyse des mots (lexmes, token en anglais) se fait systmatiquement de la
gauche vers la droite. Si plusieurs lexmes peuvent correspondre une certaine position, le
plus grand aura priorit. Considrez l'expression valide suivante :
a+++b;
Ce genre de construction hautement illisible et absconse est bien videmment viter,
mais illustre parfaitement bien ce mcanisme. Dans cet exemple, le premier lexme trouv
est bien sr l'identificateur 'a' puis le compilateur a le choix entre l'oprateur unaire '++', ou
l'oprateur binaire '+'. Le plus grand tant le premier, c'est celui-ci qui aura priorit.
L'instruction se dcompose donc en :
(a ++) + b;

Types de base
Le C est un langage typ statiquement : chaque variable, chaque constante et chaque
expression, a un type dfini la compilation. Le langage lui-mme fournit des types
permettant de manipuler des nombres (entiers, rels ou complexes) ou des caractres
(eux-mmes tant manipuls comme des entiers spciaux), et permet de construire des
types plus complexes partir de ces premiers, par exemple en groupant des donnes de
mme type en tableaux, ou des donnes de types diffrents dans des structures. Dans ce
chapitre, nous tudierons les types de base fournis par le C, l'tude des types complexes
tant faite dans la suite du livre.

Entiers
Il y a cinq types de variables entires (integer en anglais) :

char ;
short int, ou plus simplement short ;
int ;
long int, ou long ;
long long int, ou long long (ce type a t ajout depuis la norme C99).

Comme voqu en introduction, le type caractre char est particulier, et sera tudi en
dtail plus bas.
Les types entiers peuvent prendre les modificateurs signed et unsigned qui permettent
respectivement d'obtenir un type sign ou non sign. Ces modificateurs ne changent pas la
taille des types. Le langage ne dfinit pas exactement leur tailles, mais dfinit un domaine
de valeurs minimal pour chacun.
La norme C tient compte des anciennes reprsentations des nombres signs telles que le
signe+valeur absolue et le complment 1. Ces deux reprsentations sont brivement

15

Types de base

16

expliques ci-dessous :
Signe+valeur absolue (SVA)
Un bit contient le signe du nombre (par ex: 0 pour +, 1 pour -), les autres bits sont
utiliss pour la valeur absolue. On peut donc reprsenter 0 de deux manires : +0
(000...000) ou -0 (100..000). Sur N bits on peut donc reprsenter tout nombre entre
-(2N-1) (111...111) et +(2N-1) (011...111).
Complment 1 (CPL1)
Les bits des nombres ngatifs sont inverss. On peut donc reprsenter 0 de deux
manires : +0 (000...000) ou -0 (111..111). Sur N bits on peut donc reprsenter tout
nombre entre -(2N-1) (100...000) et +(2N-1) (011...111).
Ces deux reprsentations peuvent reprsenter la valeur nulle de deux manires diffrentes.
La reprsentation moderne des nombres ngatifs utilise le complment 2 (CPL2) qui
consiste reprsenter les nombres ngatifs comme le complment 1 et en ajoutant 1. Sur
un nombre fixe de bits, la valeur 0 n'a qu'une seule reprsentation : +0 (000...000) et -0
(111...111 + 1 = (1)000...000) ont deux reprsentations identiques. Sur N bits on peut donc
reprsenter tout nombre entre -(2N) (100...000) et +(2N-1) (011...111). Cette reprsentation
possde donc un domaine plus large.
Le tableau ci-dessous donne le domaine des valeurs quel que soit la reprsentation utilise
(SVA, CPL1 ou CPL2) :

Domaines de valeurs minimaux des types entiers (C90 et C99)


Type

Borne
infrieure
(formule)

Borne infrieure

Borne suprieure

Borne
suprieure
(formule)

signed char

-127

-(27-1)

+127

27-1

unsigned char

+255

28-1

short

-32767

-(215-1)

+32767

215-1

unsigned short

+65535

216-1

int

-32767

-(215-1)

+32767

215-1

unsigned int

+65535

216-1

long

-2147483647

-(231-1)

+2147483647

231-1

unsigned long

+4294967295

232-1

long long (C99)

-9223372036854775807

-(263-1)

+9223372036854775807

263-1

unsignedlonglong
(C99)

+18446744073709551615

264-1

Cette table signifie qu'un programme peut utiliser sans problme une variable de type int
pour stocker la valeur 215-1, quelque soit le compilateur ou la machine sur laquelle va
tourner le programme.
Par contre, une implmentation C peut fournir des domaines de valeurs plus larges que
ceux indiqus au-dessus :

Types de base

17

Les domaines indiqus pour les nombres signs dans le tableau prcdent sont ceux
d'une implmentation par complment 1, ou par signe et valeur absolue. Pour le
complment 2, la borne infrieure est de la forme -2N, ce qui autorise une valeur
supplmentaire (ex: int de -215 +215-1, soit de -32768 +32767),
un int implment sur 32 bits pourrait aller de -(231-1) 231-1, par exemple[1] , et un
programme tournant sur une telle implmentation peut alors utiliser ces valeurs, mais il
perdrait en portabilit.

Domaines de valeurs des types entiers reprsents en complment


2 (CPL2)
Type

Borne
infrieure
(formule)

Borne infrieure

Borne suprieure

Borne
suprieure
(formule)

signed char

-128

-(27)

+127

27-1

unsigned char

+255

28-1

short

-32768

-(215)

+32767

215-1

unsigned short

+65535

216-1

int

-32768

-(215)

+32767

215-1

unsigned int

+65535

216-1

long

-2147483648

-(231)

+2147483647

231-1

unsigned long

+4294967295

232-1

long long (C99)

-9223372036854775808

-(263)

+9223372036854775807

263-1

unsignedlonglong
(C99)

+18446744073709551615

264-1

Par ailleurs, une relation d'ordre entre ces domaines de valeurs est garantie ; qui peut tre
exprime ainsi :
domaine(char) domaine(short) domaine(int) domaine(long)
domaine(long long)
Cela signifie que toutes les valeurs possibles pour une variable du type char sont aussi
utilisables pour les autres types; mais aussi que, par exemple, une valeur valide pour le
type int peut ne pas tre reprsentable dans une variable de type short.
Si vous ne savez pas quel type donner une variable de type entier, le type int est par
dfaut le meilleur choix ( condition que votre donne ne dpasse pas 215-1) : ce type est la
plupart du temps reprsent au niveau matriel par un mot machine , c'est--dire qu'il
est adapt la taille que la machine peut traiter directement (il fait usuellement 32 bits sur
un PC 32 bits, par exemple). Cela permet un traitement plus rapide par le matriel. De plus,
beaucoup de bibliothques (que ce soit celle fournie par le langage C ou d'autres) utilisent
ce type pour passer des entiers, ce qui fait que l'utilisation de ces bibliothques sera plus
aise.
Par ailleurs, un utilisateur peut connatre les domaines de valeurs exacts de sa machine en
utilisant l'en-tte <limits.h>.

Types de base

Portabilit apporte par C99


L'incertitude sur l'intervalle de valeur de chaque type en fonction de la machine peut
s'avrer extrmement gnante, pour ne pas dire rdhibitoire. En effet, certains
programmes peuvent ncessiter un type de donnes de taille fixe et cependant tre
destins tre portables. Pour ces programmes, les types entiers du C ne sont pas
suffisants. Beaucoup d'extensions ont t rajoutes pour dfinir explicitement des types
entiers intervalle fixe (8, 16, 32 bits...) partir des types de base, avec une nomenclature
loin d'tre homogne d'un compilateur l'autre (ce qui, loin de rsoudre le problme, ne
faisait que le dplacer).
La norme ISO C99 dcide une bonne fois pour toute de dfinir, dans l'en-tte <stdint.h>,
plusieurs nouveaux types o N reprsente un nombre entier dfinissant la taille requise en
bit :
des types implments optionnellement sur certaines architectures ( viter ?) ;
entiers signs ou non et de longueur exacte : uintN_t et intN_t ;
entiers pouvant contenir un pointeur : intptr_t et uintptr_t ;
des types requis par toutes les architectures respectant la norme C99 ;
entiers devant tre plus grand que N bits au moins : int_leastN_t et uint_leastN_t
;
entiers rapides calculer et plus grand que N bits au moins : int_fastN_t et
uint_fastN_t ;
plus grand entier : intmax_t et uintmax_t.
Cet en-tte dfinit aussi des constantes pour les valeurs minimales et maximales de chaque
type.
L'include <inttypes.h> dfinit les constantes symboliques utiliser pour imprimer ces
nouveaux types avec les fonctions de la famille de printf (PRIxxx) et les lire avec celles de
scanf (SCNxxx).

Constantes numriques entires


Il existe diffrentes suites de caractres qui sont reconnues comme tant des constantes
numriques entires :
un nombre en notation dcimale : une suite de chiffres (0-9) ;
le caractre 0 suivi d'un nombre en notation octale : une suite de chiffres compris
entre 0 et 7 ;
les caractres 0x suivi d'un nombre en notation hexadcimale : une suite de chiffres
et des lettres a, b, c, d, e, f (ou A, B, C, D, E, F).
Par dfaut, une constante numrique entire est de type int et, si sa valeur est trop
grande pour le type int, elle prend celle du type plus grand suffisant. Comme les
domaines de valeurs des types peuvent varier suivant la machine, le type effectif d'une
constante peut lui aussi varier. Cela peut s'avrer problmatique lors de passage de
paramtres des fonctions nombre variable d'arguments, par exemple. cause de cela, il
est recommand de forcer le type de la constante en le postfixant des attributs suivants :
U : la constante est non-signe (voir la section promotion pour comprendre les
implications) ;
L : la constante est de type long au lieu de int ;
LL est une nouveaut C99 pour les constantes de type long long.

18

Types de base
U peut tre combin L et LL pour obtenir les types unsigned long et unsigned long
long, respectivement. Lorsqu'une constante est suffixe, mais que sa valeur est trop grande
pour le type demand, le mme processus de recherche de type assez grand est
utilis[2] .

Dbordement
Sur une machine donne, un type entier a un domaine de valeurs fixe. Considrons qu'on
travaille sur un PC en 32 bits, en complment 2 : sur un tel ordinateur, le type int varie
souvent de -231 231-1. Cela permet de manipuler sans problme des valeurs dans ce
domaine. Par contre, si on utilise des valeurs hors du domaine, par exemple 232, et qu'on
essaye de la stocker dans une variable de type int sur une telle machine, que se passe-t-il
?
La rponse dpend du type:
Si on essaye d'enregistrer une valeur hors domaine dans une variable de type sign
(comme dans l'exemple), la conversion n'est pas dfinie par le langage. Cela signifie que
tout peut arriver.
Si on essaye d'enregistrer une valeur hors domaine dans une variable de type non sign
(unsigned long, par exemple), la conversion se fait modulo la valeur maximale
reprsentable par ce type + 1.
Exemples : On suppose que le type unsigned char est cod sur 8 bits. Alors une valeur de
ce type peut aller de 0 28-1, soit 255. Par la rgle prcdente, les conversions se feront
donc modulo 255 + 1, soit 256. On considre le programme suivant :
#include <stdio.h>
int main(void)
{
unsigned char c = 300;
/* %hhu sert dire printf() qu'on veut afficher un unsigned char
*/
printf("La variable c vaut %hhu.\n", c);
c = -5;
printf("La variable c vaut %hhu.\n", c);
return 0;
}
Le rsultat du programme est :
La variable c vaut 44.
La variable c vaut 251.
En effet, on a 300 - 256 = 44 et -5 + 256 = 251.
De mme :
#include <stdio.h>
int main(void)
{
signed char c = 300;

19

Types de base
/* %hhd sert dire printf() qu'on veut afficher un signed char */
printf("La variable c vaut %hhd.\n", c);
c = -300;
printf("La variable c vaut %hhd.\n", c);
return 0;
}
Le rsultat du programme peut alors tre :
La variable c vaut 44.
La variable c vaut -44.
Sur une telle machine, les types signs sont traits de la mme manire que les types non
signs.
Si vous essayez de compiler ces deux programmes, votre compilateur pourra dtecter les
dbordements et vous en avertir (GCC le fait).

Rels
Les rels sont approxims par des nombres virgule flottante. Comme dans le cas des
entiers, il existe plusieurs types de nombre virgule flottante. En voici la liste trie par
prcision croissante :
float ;
double ;
long double.
La norme C90 tait assez floue concernant les nombres virgule flottante, leurs
reprsentations, la prcision des oprations, etc., ce qui fait que c'tait un des domaines o
la conception de programmes utilisant les nombres flottants tait chose peu aise. Le C99 a
clarifi les choses en prcisant qu'une implmentation C devait respecter la norme IEC
60559:1989 Arithmtique binaire en virgule flottante pour systmes
microprocesseur. Cette norme (drive de IEEE 754) dfinit des formats de donnes pour
les nombres virgule flottante, ainsi que des oprations et fonctions sur ces nombres. Elle
garantit, entre autres :
que certains types de donnes auront toujours le mme format ;
et que les calculs effectus sur un type de donne donneront toujours le mme rsultat.
Elle dfinit de plus comment sont grs les cas exceptionnels, comme les infinis, les NaN
(pour Not a Number, rsultant par exemple de la division 0/0), etc.
Les types flottants du C correspondent aux type IEC 60559 de la manire suivante :
float : simple prcision
double : double prcision
long double : suivant l'implmentation, soit la double prcision tendue, soit un type
non-IEC 60559 (mais de prcision au moins gale double), soit double prcision.

20

Types de base

Constantes relles
Une suite de caractres reprsente une constante virgule flottante si :
c'est une suite de chiffres spare par un caractre point , cette sparation pouvant
s'effectuer n'importe quel endroit de la suite (0.0, .0 et 0. reprsentent tous les trois
la valeur 0 de type double) ;
un nombre suivi d'un caractre e suivi d'un entier.
Dans le deuxime cas, le nombre peut tre soit un entier, soit un rel du premier cas.
Les constantes sont de type double par dfaut. Pour demander le type float, il faut la
suffixer par f ou F, et pour le type long double par l ou L.

Arithmtique
Une attention particulire doit tre porte sur la prcision des types rels. Ces diffrents
types ne font qu'approximer l'ensemble des nombres rels, avec une prcision finie. Des
erreurs d'arrondis sont prvoir, ce qui est trs problmatique pour des domaines qui n'en
tolrent pas (notamment pour les applications financires, il est conseill de ne pas utiliser
de calcul en virgule flottante).
Le type float en particulier a une prcision minimale, qui est bien souvent insuffisante.
Voici un exemple classique d'erreur ne pas faire qui illustre les problmes lis la
prcision des types flottants :
#include <stdio.h>
int main(void)
{
float i = 0;
int j;
for (j = 0; j < 1000; j ++)
{
i += 0.1;
}
printf("i = %f\n", i);
return 0;
}
Le rsultat est 99,999046, ce qui montre que la prcision du type float est en gnral
mauvaise, d'autant plus que le nombre 0,1 n'est pas reprsentable en binaire. Il est ainsi
conseill d'utiliser le type double la place de float autant que possible. Dans ce cas de
figure, il est prfrable d'viter les accumulations d'erreurs infinitsimales, en rcrivant le
code de la manire suivante :
#include <stdio.h>
int main(void)
{
int j;
for (j = 0; j < 1000; j++)
{

21

Types de base
float i = j * 0.1;
}
return 0;
}
C'est probablement plus coteux, car on effectue un millier de multiplications au lieu d'un
millier d'additions, mais cela permet d'avoir une prcision nettement meilleure que le code
prcdent.
Pour plus d'informations sur ce domaine, un wikilivre Arithmtique flottante est disponible.

Caractres
Le type permettant de reprsenter un caractre est char. Ce type est un peu plus
particulier que les autres, d'une part parce que sa taille dfinit l'unit de calcul pour les
quantits de mmoire (et donc pour les tailles des autres types du langage) et d'autre part
son domaine de valeur peut grandement varier de manire relativement inattendue.
Par dfinition, la taille du type char, note sizeof(char), vaut toujours 1. Cependant, il
faut faire attention : contrairement ce qu'on pense souvent, un char au sens du C ne vaut
pas toujours un octet. Il occupera au minimum 8 bits, mais il existe des architectures,
relativement spcialises il est vrai, ayant des char de 9 bits, de 16 bits, voire plus. Mme
si, dans une large majorit des cas, les compilateurs utilisent des char de 8 bits, la fois
par simplicit (les machines modernes fonctionnent gnralement en 8, 16, 32 ou 64 bits)
et pour viter des problmes de portabilit de code (beaucoup de codes C existants
reposent sur l'hypothse que les char font 8 bits, et risqueraient de ne pas marcher sur
une telle architecture)[3] . Par simplification, nous utiliserons donc le terme octet la plupart
du temps dans la suite de ce wikilivre.
Un autre pige de ce type est qu'il peut tre de base signed ou unsigned, au choix du
compilateur, ce qui peut s'avrer dangereux. Considrez le code suivant:
/* Ce code peut ne pas fonctionner avec certains compilateurs */
char i;
for (i = 100; i >= 0; i --)
{
/* ... */
}
Dans cet exemple, l'instruction for permet de faire itrer les valeurs entires de i de 100
0, incluses. On pourrait navement penser optimiser en utilisant un type char. Sauf que
si ce type est implicitement unsigned, la condition i >= 0 sera toujours vraie, et tout ce
que vous obtiendrez est une boucle infinie. Normallement tout bon compilateur devrait
vous avertir que la condition est toujours vraie et donc vous permettre de corriger en
consquence, plutt que perdre des heures en dbugage.

22

Types de base

23

Constantes reprsentant un caractre


Une constante reprsentant un caractre est dlimite par des apostrophes, comme par
exemple 'a'. En fait pour le C, les caractres ne sont ni plus ni moins que des nombres
entiers (ils sont de type int, mais leur valeur tiendra dans un type char), les deux tant
parfaitement interchangeables. Il est tout fait autoris d'crire 'a' * 2 ou 'a' - 32. La
valeur reprsente par cette constante est nanmoins dpendante du systme, mme si
dans une crasante majorit des cas, on se retrouvera avec un jeu de caractre compatible
avec ASCII, qui dfinit prcisment les glyphes des caractres de 0 127[4] . Cet ensemble
est peine suffisant pour couvrir certaines langues latines, seule optique vise l'poque
o il a t dfini, si bien que de nombreuses extensions sont par la suite apparues.
Par exemple le caractre (ligature du o et du e) a pour valeur :
189 dans le jeu de caractres ISO-8859-15 (principalement utilis pour les langues
latines d'Europe) ;
156 sur certaines variantes du jeu de caractres Windows 1252 ;
0xc593 (deux octets) si votre diteur de texte utilise l'UTF-8 ;
207 avec l'encodage Mac Roman (Mac OS 9 et antrieur) ;
Et n'a pas d'quivalent en ISO-8859-1 (le caractre 189 est le symbole , le 207 est le
et le 156 n'est pas utilis).
Il s'agit en fait d'un cas particulier d'un sujet trs vaste, qui porte le nom
d'internationalisation. Le langage C reste relativement agnostique ce niveau : les
caractres sont des nombres et les chanes, une simple suite de caractres termine par 0.
Au systme d'interprter les nombres comme il se doit.
Un petit exemple :
#include <stdio.h>
int main(void)
{
printf("Sur votre machine, la lettre 'a' a pour code %d.\n", 'a');
return 0;
}
Le programme prcdent donnera le rsultat suivant sur une telle machine :
Sur votre machine, la lettre 'a' a pour code 97.

Caractres spciaux
Il existe certaines constantes caractres aux significations particulires:
Constante

Caractre

'\''

une apostrophe

'\"'

un guillemet

'\?'

un point d'interrogation

'\\'

un backslash

'\a'

un signal sonore (ou visuel)

'\b'

un espace arrire

Types de base

24

'\f'

saut au dbut de la page suivante

'\n'

saut de ligne

'\r'

un retour chariot

'\t'

une tabulation

'\v'

une tabulation verticale

De plus, on peut crire n'importe quelle valeur de caractre avec les expressions suivantes:
'\xHH', o chaque H reprsente un chiffre hexadcimal correspondant au code du
caractre. Par exemple, '\x61' reprsente le caractre 'a' (minuscule) en ASCII (car
97 = 6 * 16 + 1);
'\oOO', o chaque O reprsente un chiffre octal correspondant au code du caractre.

Trigraphe
Cette fonctionnalit obscure et peu employe est en gnral viter, car trs souvent
inutile, et peut causer des bugs incomprhensibles au programmeur non averti. Nanmoins,
de trs rares programmes peuvent utiliser ce genre de fonctionnalits. Un trigraphe est
simplement une suite de trois caractres dans le code source qui sera remplace par un
seul ; cette fonctionnalit a t ajoute au C pour supporter les architectures dont
l'alphabet ne dispose pas de certains caracres qui sont ncessaires dans la syntaxe du C,
comme les dises ou les accolades.
Les substitutions suivantes sont faites partout dans le code source (y compris les chanes de
caractres):
Trigraphe

Caractre

??=

??(

??)

??<

??>

??/

??'

??!

??-

Voici une manire de rendre illisible un programme utilisant les trigraphes:


??=include <stdio.h>
int main(void)
??<
puts("Bonjour !");
return 0;
??>
noter que, par dfaut, la plupart des compilateurs dsactivent les trigraphes, au cas o
vous ne seriez pas encore dissuad de les utiliser.

Types de base

Chane de caractres
Une chane de caractre, comme son nom l'indique, est une suite de caractres avec la
particularit d'avoir un caractre nul (0) la fin. Une chane de caractre est en fait
implmente en C avec un tableau de type char.
Sous sa forme la plus simple, on dclare une chane comme une suite de caractres entre
guillemets (double quote) :
"Ceci est une chane de caractre";
""; /* Chane vide */
Si la chane est trop longue, on peut aussi la couper sur plusieurs lignes :
"Ceci est une chane de caractre, " /* pas de ; */
"dclare sur plusieurs lignes.";
Deux chanes ctes ctes (modulo les espaces et les commentaires) seront concatnes
par le compilateur. De plus on peut utiliser le caractre barre oblique inverse (\) pour
annuler la signification spciale de certains caractres ou utiliser des caractres spciaux
(C.f liste ci-dessus).
"Une chane avec des \"guillemets\" et une barre oblique (\\)\n";
Il est aussi possible d'utiliser la notation hexadcimale et octale pour dcrire des caractres
dans la chane, il faut nanmoins faire attention avec la notation hexadcimale, car la
dfinition peut s'tendre sur plus de 2 caractres. En fait, tous les caractres suivant le \x
et appartenant l'ensemble "0123456789abcdefABCDEF" seront utiliss pour dterminer la
valeur du caractre (du fait que les caractres d'une chane peuvent faire plus qu'un octet).
Cela peut produire certains effets de bords comme:
/* Ce code contient un effet de bord inattendu */
"\x00abcdefghijklmnopqrstuvzxyz"
Ce code n'insrera pas un caractre 0 au dbut de la chaine, mais toute la squence
"\x00abcdef" servira calculer la valeur du caractre (mme si le rsultat sera tronqu
pour tenir dans un type char). On peut viter cela en utilisant la concatnation de chanes
constantes :
"\x00" "abcdefghijklmnopqrstuvzxyz"
Enfin, les chanes de caractres faisant appel aux concepts de pointeur, tableau et de zone
mmoire statique, leur utilisation plus pousse sera dcrite dans la section ddie aux
tableaux.
noter la reprsentation obsolte des chanes de caractres multilignes. viter dans la
mesure du possible :
/* Ce code est obsolete et a eviter */
"Ceci est une chane de caractre,\
dclare sur plusieurs lignes";

25

Types de base

Boolens
Le langage (jusqu' la norme C99) ne fournit pas de type boolen. La valeur entire 0 prend
la valeur de vrit faux et toutes les autres valeurs entires prennent la valeur de vrit
vrai.
La norme C99 a introduit le type _Bool, qui peut contenir les valeurs 0 et 1. Elle a aussi
ajout l'en-tte <stdbool.h>, qui dfinit le type bool qui est un raccourci pour _Bool, et
les valeurs true et false[5] . Nanmoins, ces nouveauts du C99 ne sont pas trs utilises,
les habitudes ayant t prises d'utiliser 0 et diffrent de zro pour les boolens en C.
Nota : toute expression utilisant des oprateurs boolens (voir oprateurs), retourne 1 si
l'expression est vraie et 0 si elle est fausse, ce qui rend quasiment inutile l'usage du type
boolen.

Vide
En plus de ces types, le langage C fournit un autre type, void qui reprsente rien, le vide.
Il n'est pas possible de dclarer une variable de type void. Nous verrons l'utilit de ce type
lorsque nous parlerons de fonctions et de pointeurs.

Rfrences
[1] La norme C contraint les domaines de valeurs des types signs entre -(2N-1) et 2N-1, o N est un entier
quelconque, et les types non signs entre 0 et 2N-1.
[2] La liste des types successifs utiliss pour dterminer le type d'une constante entire a chang entre C90 et
C99. Cela n'a, quasiment tout le temps, aucune incidence, sauf pour le cas d'une constante de valeur trop
grande pour le type long : en C90, le type unsigned long est utilis, alors qu'en C99 le type long long sera
utilis. Une telle valeur sera alors signe dans un cas, et non signe dans l'autre. Dans ce cas, utiliser un suffixe
adapt (UL, LL ou ULL) permet de s'assurer du type de la constante.
[3] La confusion est surtout ne de l'utilisation du mot byte pour dsigner la taille du type char dans la norme,
alors que ce mot est utilis dans presque tous les autres domaines de l'informatique pour dsigner un octet.
[4] En-dehors des encodages compatibles avec ASCII, on ne rencontre gure en pratique que la famille de jeux de
caractres EBCDIC, utilise sur des systmes IBM et quelques mainframes.
[5] Ces trois expressions sont dfinies sous formes de macro pour le prprocesseur.

26

Classe de stockage

Classe de stockage
Il est possible de construire des types drivs des types de base du langage en utilisant
plusieurs combinaisons, deux tant illustres dans ce chapitre: les classes de stockage et
les qualificateurs.

Classe de stockage
Le langage C permet de spcifier, avant le type d'une variable, un certain nombre de
classes de stockage :
auto : pour les variables locales ;
extern : dclare une variable sans la dfinir ;
register : demande au compilateur de faire tout son possible pour utiliser un registre
processeur pour cette variable ;
static : rend une dfinition de variable persistante.
Les classes static et extern sont, de loin, les plus utilises. register est d'une utilit
limite, et auto est maintenant obsolte.
Une variable, ou un paramtre de fonction, ne peut avoir qu'au plus une classe de stockage.

Classe 'static'
L'effet de la classe 'static' dpend de l'endroit o l'objet est dclar :
Objet local une fonction : la valeur de la variable sera persistante entre les diffrents
appels de la fonction. La variable ne sera visible que dans la fonction, mais ne sera pas
rinitialise chaque appel de la fonction. L'intrt est de garantir une certaine
encapsulation, afin d'viter des usages multiples d'une variable globale. Qui plus est, cela
permet d'avoir plusieurs fois le mme nom, dans des fonctions diffrentes.
Exemple :
#include <stdio.h>
void f(void)
{
static int i = 0; /* i ne sera initialis 0 qu'au premier appel
de f */
int j = 0; /* j sera initialis chaque fois */;
i++;
j++;
printf("i vaut %d et j vaut %d.\n", i, j);
}
int main(void)
{
f();
f();
f();
return 0;

27

Classe de stockage
}
Objet global et fonction : comme une variable globale est dj persistante, le mot-cl
static aura pour effet de limiter la porte de la variable ou de la fonction au seul fichier
o elle est dclare, toujours dans le but de garantir un certain niveau d'encapsulation.
faire...
utilisation de static en C99 pour les tableaux en paramtres de fonctions

Classe 'extern'
extern permet de dclarer une variable sans la dfinir. C'est utile pour la compilation
spare, pour dfinir une variable ou une fonction dans un fichier, en permettant des
fonctions contenues dans d'autres fichiers d'y accder.
Toutes les variables globales et fonctions qui ne sont pas dclares (ou dfinies) static
sont externes par dfaut.
static et extern sont employs pour distinguer, dans un fichier C, les objets et fonctions
publics , qui pourront tre accessibles depuis d'autres fichiers, de ceux qui sont privs
et ne doivent tre utiliss que depuis l'intrieur du fichier.

Classe 'register'
Indique que la variable devrait tre stocke dans un registre du processeur. Cela permet de
gagner en performance par rapport des variables qui seraient stockes dans un espace
mmoire beaucoup moins rapide, comme une pile place en mmoire vive.
Ce mot-cl a deux limitations principales :
Les registres du processeur sont forcment limits, aussi bien en nombre qu'en taille. Il
est donc inutile de dclarer une structure entire ou un tableau avec le mot cl register.
Qui plus est, les variables places dans des registres sont forcment locales des
fonctions ; on ne peut pas dfinir une variable globale en tant que registre.
Aujourd'hui, ce mot-cl est dconseill sauf pour des cas particuliers, les compilateurs
modernes sachant gnralement mieux que le programmeur comment optimiser et quelles
variables placer dans les registres.
#include <stdio.h>
int main(void)
{
register short i, j;
for (i = 1; i < 1000; ++i)
{
for(j = 1; j < 1000; ++j)
{
printf("\n %d %d", i, j);
}
}
return 0;

28

Classe de stockage
}

Classe 'auto'
Cette classe est un hritage du langage B. En C, ce mot-cl sert pour les variables locales
une fonction non-statiques, dites aussi automatiques. Mais une variable dclare
localement une fonction sans qualificateur static tant implicitement automatique, ce
mot-cl est inutile en C.

Qualificateurs
Le C dfinit trois qualificateurs pouvant influer sur une variable :
const : pour dfinir une variable dont la valeur ne devrait jamais changer ;
restrict : permet une optimisation pour la gestion des pointeurs ;
volatile : dsigne une variable pouvant tre modifie notamment par une source
externe indpendante du programme.
Une variable, ou un paramtre de fonction, peut avoir aucun, un, deux, ou les trois
qualificateurs (certaines combinaisons n'auraient que peu de sens, mais sont autorises).

Qualificateur 'const'
La classe const ne dclare pas une vraie constante, mais indique au compilateur que la
valeur de la variable ne doit pas changer. Il est donc impratif d'assigner une valeur la
dclaration de la variable, sans quoi toute tentative de modification ultrieure entrainera
une erreur de la part du compilateur :
Ce code contient une erreur volontaire !
const int i = 0;
i = 1; /* erreur*/
En fait, le mot-cl const est beaucoup plus utilis avec des pointeurs. Pour indiquer qu'on
ne modifie pas l'objet point, il est bon de spcificier le mot-cl const :
void fonction( const char * pointeur )
{
pointeur[0] = 0;
/* erreur*/
pointeur
= "Nouvelle chaine de caractres";
}
Dans cet exemple, on indique que l'objet point ne sera pas modifi. Pour indiquer que la
valeur elle-mme du pointeur est constante, il faut dclarer la variable de la sorte :
Ce code contient une erreur volontaire !
char * const pointeur = "Salut tout le monde !";
pointeur = "Hello world !"; /* erreur*/
Encore plus subtil, on peut mlanger les deux :
Ce code contient plusieurs erreurs volontaires !
const char * const pointeur = "Salut tout le monde !";
pointeur = "Hello world !"; /* erreur*/
pointeur[0] = 0; /* erreur*/

29

Classe de stockage
Cette dernire forme est nanmoins rarement usite. En outre ce dernier exemple prsente
un autre problme qui est la modification d'une chaine de caractres en dur , qui sont la
plupart du temps places dans la section lecture seule du programme et donc inaltrables.

Qualificateur 'volatile'
Ce mot-cl sert spcifier au compilateur que la variable peut tre modifie son insu.
Cela annule toute optimisation que le compilateur pourrait faire, et l'oblige procder
chaque lecture ou criture dans une telle variable tel que le programmeur l'a crit dans le
code. Ceci a de multiples utilisations :
pour les coordonnes d'un pointeur de souris qui seraient modifies par un autre
programme ;
pour la gestion des signaux (voir Gestion des signaux) ;
pour de la programmation avec de multiples fils d'excution qui doivent communiquer
entre eux ;
pour dsigner des registres matriels qui peuvent tre accds depuis un programme C
(une horloge, par exemple), mais dont la valeur peut changer indpendamment du
programme ;
etc.
On peut combiner const et volatile dans certaines situations. Par exemple :
extern const volatile int horloge_temps_reel;
dclare une variable entire, qu'on ne peut modifier partir du programme, mais dont la
valeur peut changer quand mme. Elle pourrait dsigner une valeur incrmente
rgulirement par une horloge interne.

Qualificateur 'restrict'
Introduit par C99, ce mot-cl s'applique aux dclarations de pointeurs uniquement. Avec
restrict, le programmeur certifie au compilateur que le pointeur dclar sera le seul
pointer sur une zone mmoire. Cela permettra au compilateur d'effectuer des optimisations
qu'il n'aurait pas pu deviner autrement. Le programmeur ne doit pas mentir sous peine de
problmes...
Par exemple :
int* restrict pZone;

30

Oprateurs

31

Oprateurs
Les oprateurs du C
Les oprateurs du C permettent de former des expressions, expressions qui diront quoi
faire votre programme. On peut voir un programme C comme tant compos de trois
catgories d'instructions:
Les dclarations et dfinitions (variables, fonctions, types) : dclarent et dfinissent
les objets que pourront manipuler le programme.
Les expressions : manipulent les dclarations, via les oprateurs.
Les instructions : manipulent les expressions pour leur donner une signification
particulire (test, boucle, saut, ...).
Les dclarations de variables ont en partie t dcrites dans les chapitres prcdents. Les
expressions seront en partie dcrites dans celui-ci (les oprateurs lis aux pointeurs,
tableaux, structures et fonctions seront dcrit dans des chapitres ddis), et les instructions
seront dcrites au cours des chapitres suivants.
Les expressions en C sont en fait trs gnriques, elles peuvent inclure des oprations
arithmtiques classiques (addition, soustraction, division, modulo, ...), des expressions
boolennes (OU logique, ET logique, OU exclusif, ...), des comparaisons (galit, ingalit,
diffrence, ...) et mme des affectations (copie, auto-incrment, ...). Toutes ces oprations
peuvent s'effectuer en une seule expression, il suffit d'appliquer les bons oprateurs sur les
bons oprandes.
Les oprateurs binaires et ternaires utilisent une notation infixe (l'oprateur se trouve
entre les 2 ou 3 oprandes). Les oprateurs unaires s'crivent de manire prfix (avant
l'oprande), l'exception des oprateurs ++ et -- qui peuvent s'crire de manire prfixe
ou postfixe (avec une diffrence subtile, dcrite ci-aprs).
La priorit (quel oprateur est appliqu avant, en l'absence de parenthses explicite) et
l'associativit (dans quel ordre sont traits les arguments des oprateurs ayant la mme
priorit) sont rsumes dans la table suivante (par ordre dcroissant de priorit - les
oprateurs dcrits dans un autre chapitre ont un lien ddi):
oprateur

arit

associativit

description

( )

gauche vers la droite


(GD)

parenthsage

() [] . ->

GD

appel de fonction, index de tableau, membre


de structure, pointe sur membre de structure

unaire

droite vers la gauche


(DG)

ngation boolenne

unaire

DG

ngation binaire

++ --

unaire

DG

incrmentation et dcrmentation

unaire

DG

oppos

(type)

unaire

DG

oprateur de transtypage (cast)

unaire

DG

oprateur de drfrenage

&

unaire

DG

oprateur de rfrenage

Oprateurs

32

sizeof

unaire

DG

fournit la taille en nombre de "char" de


l'expression (souvent en octet mais pas
toujours, mais sizeof(char) == 1 par
dfinition, voir Caractres)

* / %

binaire

GD

multiplication, division, modulo (reste de la


division)

+ -

binaire

GD

addition, soustraction

>> <<

binaire

GD

dcalages de bits

> >= < <=

binaire

GD

comparaisons

== !=

binaire

GD

galit/diffrence

&

binaire

GD

et binaire

binaire

GD

ou exclusif binaire

binaire

GD

ou inclusif binaire

&&

binaire

GD

et logique avec squencement

||

binaire

GD

ou logique avec squencement

? :

ternaire

DG

si...alors...sinon

= += -= *= /= %= ^= &= |=
>>= <<=

binaire

DG

affectation

binaire

GD

squencement

Post/pr incrmentation/dcrmentation
C'est un concept quelque peu sibyllin du C. Incrmenter ou dcrmenter de un est une
opration extrmement courante. crire chaque fois 'variable = variable + 1'
peut-tre trs pnible longue. Le langage C a donc introduit des oprateurs raccourcis
pour dcrmenter ou incrmenter n'importe quel type atomique (grable directement par le
processeur : c'est dire pas un tableau, ni une structure ou une union). Il s'agit des
oprateurs '++' et '--', qui peuvent tre utiliss de manire prfixe ou postfixe (avant ou
aprs la variable).
Utilis de manire prfixe, l'oprateur incrmente/dcrmente la variable, puis retourne la
valeur de celle-ci. En fait, les expressions (++E) et (--E) sont quivalentes respectivement
aux expressions (E+=1) et (E-=1).
Par contre, utilis de manire postfixe, l'oprateur retourne la valeur originale avant de
modifier la valeur de la variable.
int i = 0, j;
j = i++; /* j vaut 0 et i vaut 1 */
j = --i; /* j vaut -1 et i vaut -1 */
Il est important de noter que le langage ne fait que garantir qu'une variable
post-incrmente ou post-dcrmente acquiert sa nouvelle valeur entre le rsultat de
l'oprateur et le prochain point de squencement atteint (gnralement la fin d'une
instruction). Mais on ne sais pas vraiment quand. Ainsi, si l'objet sur lequel s'applique un
tel oprateur apparat plusieurs fois avant un point de squencement, alors le rsultat est
imprvisible et son comportement peut changer simplement en changeant les options d'un

Oprateurs
mme compilateur:
Ce code contient plusieurs erreurs volontaires !
/* Le code qui suit est imprvisible */
i = i++;
/* Ou de faon moins vidente */
j = f(++i, 22, i);

Promotion entire
La promotion entire, ne pas confondre avec la conversion automatique de type, fait que
tous les types plus petits ou gaux int (char, short, champs de bits, type numr) sont
convertis (promus) en int ou unsigned avant toute opration. Ainsi, dans l'exemple
suivant, a + b est calcul avec des int et le rsultat est de type int :
short sum(short a, short b) {
return a + b; /* quivaut : return (int)a + (int)b; */
}
Le compilateur peut mettre un avertissement du fait que le rsultat int est converti en
short pour tre retourn, et cette conversion peut causer une perte de prcision (par
exemple si int a une largeur de 32 bits et short de 16 bits).
La promotion se fait vers unsigned lorsqu'un int ne peut pas reprsenter le type promu.

Conversion automatique
La conversion est un mcanisme qui permet de convertir implicitement les nombres dans le
format le plus grand utilis dans l'expression.
L'exemple classique est le mlange des nombres rels avec des nombres entiers. Par
exemple l'expression '2 / 3.' est de type double et vaudra effectivement deux tiers
(0,666666...). L'expression '2 * a / 3' calculera les deux tiers de la variable 'a', et
arrondira automatiquement l'entier par dfaut, les calculs ne faisant intervenir que des
instructions sur les entiers (en supposant que 'a' soit un entier). noter que l'expression '2
/ 3 * a' vaudra... toujours zro !
Plus subtile, l'expression '2 * a / 3.', en supposant toujours que a soit un entier,
effectuera une multiplication entire entre 2 et a, puis promouvra le rsultat en rel
(double) puis effectuera la division relle avec trois, pour obtenir un rsultat rel lui-aussi.
Enfin un cas o les promotions peuvent surprendre, c'est lorsqu'on mlange des entiers
signs et non-signs, plus particulirement dans les comparaisons. Considrez le code
suivant :
/* Ce code contient un effet de bord sibyllin */
unsigned long a = 23;
signed char b = -23;
printf( "a %c b\n", a < b ? '<' : (a == b ? '=' : '>') );
De toute vidence a est suprieur b dans cet exemple. Et pourtant, si ce code est excut
sur certaines architectures, il affichera a < b, justement cause de cette conversion.

33

Oprateurs
Tout d'abord dans la comparaison a < b, le type de a est le plus grand , donc b est
promu en unsigned long. C'est en fait a le problme: -23 est une valeur ngative, et la
conversion d'une valeur ngative en un type non sign se fait modulo la valeur maximale
reprsentable par le type non sign + 1. Si la valeur maximale pour le type unsigned long
32
32
32
est 2 -1, alors -23 est converti modulo 2 , et devient 4294967273 (soit 2 -23). Dans ce
cas l effectivement 23 < 4294967273, d'o ce rsultat surprenant.
noter qu'on aurait eu le mme problme si le type de b tait signed long. En fait les
types signs sont promus en non-signs, s'il y a au moins une variable non-signes dans
l'expression, et que le type non-sign est "plus grand" que le type sign.
Dans ce cas prsent, il faut effectuer soi-mme le transtypage :
printf( "a %c b\n", (long) a < b ? '<' : ((long)a == b ? '=' : '>') );
La norme dfinit prcisment les conversions arithmtiques et les promotions entires, se
rfrer elle pour les dtails.

valuation des expressions boolennes


Le C ne possde pas de type boolen ddi[1] . Dans ce langage, n'importe quelle valeur
diffrente de zro est considre vraie, zro tant considr comme faux. Ce qui veut dire
que n'importe quelle expression peut tre utilise l'intrieur des tests (entier, rels,
pointeurs, tableaux, etc.). Cela peut conduire des expressions pas toujours trs claires,
comme :
int a;
a = une_fonction();
if (a)
{
/* ... */
}
Ce type d'criture simplifie a ses adeptes, mais elle peut aussi s'crire de la manire
suivante:
int a;
a = une_fonction();
if (a != 0)
{
/* ... */
}
Cette seconde criture, smantiquement quivalente la premire, rend explicite le test
qui est effectu (on compare la valeur de la variable a zro).
Par ailleurs, cette absence de type boolen rend aussi valide le code suivant:
int a = 0;
int b = 2;
if (a = b)
{
/* Le code qui suit sera toujours excut ... */

34

Oprateurs
}
Dans cet exemple, il y a un seul caractre = entre a et b, donc on ne teste pas si a est gal
b, mais on affecte la valeur de la variable b la variable a. Le rsultat de l'expression
tant 2, elle est donc toujours considre vraie. Ce genre de raccourci est en gnral
viter. En effet, il est facile de faire une faute de frappe et d'oublier de taper un caratre =.
Comme le code rsultant est toujours valide au sens du C, cette erreur peut ne pas tre vue
immdiatement. Pour aider les dveloppeurs dtecter ce genre d'erreurs, de nombreux
compilateurs mettent un avertissement quand un tel code leur est prsent. Une manire
de faire taire ces avertissements, une fois qu'on s'est assur qu'il ne s'agit pas d'une erreur,
est de parenthser l'affectation:
/* On veut faire une affectation ici. */
/* double parenthsage pour supprimer l'avertissement du compilateur*/
if ((a = b))
{
/* ... */
}
Comme un dveloppeur devant maintenir un programme contenant un test if (a = b) {
/* ... */ } se demandera si le dveloppeur prcdent n'a pas fait une faute de frappe, il
est prfrable d'viter autant que possible ce genre de situations et, s'il est ncessaire (ce
qui a trs peu de chances d'arriver), de la commenter. En effet, mme un code if ((a =
b)) { /* ... */ } non comment doit tre lu avec attention, car le dveloppeur
prcdent peut avoir ajout les parenthses juste pour faire taire le compilateur, sans se
rendre compte d'une erreur. Dans tous les cas, la manire la plus sre est de dcomposer
ainsi:
a = b;
if (a != 0)
{
/* ... */
}
Une autre technique classique, lorsqu'une comparaison fait intervenir une constante, est de
mettre la constante gauche. De cette manire, si la comparaison se transforme par
mgarde en affectation, cela provoquera une erreur la compilation:
if (0 == b)
{
/* Une instruction "0 = b" ne passerait pas */
}
Les oprateurs logiques de comparaisons (&& et ||, similaires smantiquement leur
quivalent binaire & et |) valuent leurs oprandes en circuit court. Dans le cas du ET
logique (&&), si l'oprande gauche s'value faux, on sait dj que le rsultat du ET sera
faux et donc ce n'est pas la peine d'valuer l'oprande droite. De la mme manire si
l'oprande gauche d'un OU logique (||) est valu vrai, le rsultat sera aussi vrai et donc
l'valuation de l'oprande droite est inutile. Ceci permet d'crire des expressions de ce
genre, sans gnrer d'exception de l'unit arithmtique du processeur :

35

Oprateurs
if (z != 0 && a / z < 10)
{
printf("Tout va bien\n");
}

Rfrences
[1] La norme C99 a ajout le type _Bool, mais celui-ci est peu utilis, l'usage des types entiers pour reprsenter
les boolens en C tant trs rpandu (voir Boolens).

Structures de contrle -tests


Les tests permettent d'effectuer des oprations diffrentes suivant qu'une condition est
vrifie ou non.

Test if
Syntaxe
if (condition)
instructions
else
autre instructions
Si la condition est vrifie, alors on excute le bloc d'instructions, sinon on excute lautre
bloc. La clause else est facultative, qui plus est, une clause else' se rfre toujours
la dernire instruction if rencontre. Considrez le code, mal indent, suivant :
/* Ce code est volontairement indent de manire ambigu */
if (condition)
if (autre_condition) /* ... */ ;
else
/* ... */ ;
Dans ce dernier exemple la clause else se rapportait bien-sr au second if selon la rgle
nonce ci-dessus. Un bon compilateur devrait vous avertir que cette construction est
ambigu, et que vous devriez la rcrire :
if (condition)
{
if (autre_condition) /* ... */ ;
}
else /* ... */ ;
D'ailleurs, lorsqu'un if contient une instruction (for, do, while, switch, goto, return, etc.)
il est conseill de la mettre entre accolades.

36

Structures de contrle - tests

Test switch
Cette instruction permet de tester si une expression concide avec un certain nombre de
constantes, et d'excuter une action par dfaut dans le cas o aucune valeur ne correspond
celle de l'expression. Cela permet un traitement beaucoup plus efficace et lisible qu'une
succession de if/else imbriqus. Insistons sur le terme constante: il est en effet
impossible d'utiliser des expressions dont la valeur n'est pas connue la compilation (c'est
dire de variable dans les instructions case).

Syntaxe
switch (expression)
{
case valeur1:
bloc1
case valeur2:
bloc2
/*...*/
case valeurN:
blocN
default:
blocD
}
Compare la valeur de l'expression celles de valeur1, valeur2, ..., valeurN. En cas d'galit
entre expression et valeurI les blocs sont excuts squentiellement partir de blocI, et ce
jusqu' la fin de l'instruction switch. Si expression est gale valeur2, dans cet exemple,
les blocs bloc2 blocN et mme blocD seront excuts. Pour empcher ce comportement
on utilise l'instruction break, que l'on peut placer n'importe quel endroit pour sortir (aller
la fin) de l'instruction switch. En gnral, on retrouve plus frquemment l'instruction
switch crite de la sorte :
switch (expression)
{
case valeur1:
bloc1
break;
case valeur2:
bloc2
break;
/*...*/
case valeurN:
blocN
/* pas de break; */
default:
blocD
}
C'est en fait tellement rare de ne pas mettre de break entre les diffrents cas, qu'il est
conseill de mettre un commentaire pour les cas o cette instruction est dlibrment

37

Structures de contrle - tests

38

omise, a permet de bien signaler au lecteur qu'il ne s'agit pas d'un oubli.

Expression conditionnelle
test ? expression_si_vrai : expression_si_faux

Exemple
#include <stdio.h>

int main(int argc, char * argv[])


{
printf("%s\n", argc < 2 ? "Vous n'avez pas donn d'argument." : "Vous avez donn au m
return 0;
}
Ce mini-programme teste si le nombre d'arguments pass main est infrieur 2 avec
l'expression argc < 2 et renvoie "vous n'avez pas donn d'argument" si l'expression
est vraie et "vous avez donn au moins un argument" sinon. Le rsultat de l'valuation
est alors pass la fonction printf qui affiche le rsultat.

Structures de contrle -itrations


Boucle for
Syntaxe
for (initialisation ; condition ; itration)
bloc
Une boucle for commence par l' initialisation, puis excute le bloc de code tant que la
condition est vrifie et en appelant l'itration aprs chaque bloc.
On peut tout de suite remarquer que la boucle for du C est beaucoup plus puissante qu'une
boucle for classique, dans le sens o elle ne correspond pas forcment un nombre fini de
tours de boucle.
La norme C99 permet maintenant de dclarer des variables dans la partie initialisation de
la boucle for. Ces variables ne peuvent qu'tre de classe automatic ou register. Leur
porte est limite la boucle. Elles n'interfrent pas avec les variables de mme nom
dclares en dehors de la boucle. Leur utilisation pourrait conduire de possibles
optimisations du code par le compilateur.

Exemples
Exemple 1
int i;
for (i = 0 ; i < 10 ; i++)
{
printf("%d\n", i);

Structures de contrle - itrations


}
En C99 :
for (int i = 0 ; i < 10 ; i++)
{
printf("%d\n", i);
}
Cet exemple est une boucle for normale, l'indice de boucle i est incrment chaque tour
et le bloc ne fait qu'afficher l'indice. Ce code affiche donc les 10 premiers entiers en partant
de 0. Le bloc de code tant rduit une instruction, on aurait pu crire :
int i;
for (i = 0 ; i < 10 ; i++)
printf("%d\n", i);
Exemple 2
int i = 0;
for( ; i != 1 ; i = rand() % 4)
{
printf("je continue\n");
}
Ce deuxime exemple montre plusieurs choses :
1. l'initialisation de la boucle a t omise, en fait n'importe laquelle des trois parties peut
l'tre (et mme les trois la fois, ce qui rsulte en une boucle infinie) ;
2. l'expression itration est rellement une expression, on peut y faire appel des
fonctions ;
3. le nombre d'itrations n'est pas fixe, le programme affichera je continue un nombre
(pseudo-)alatoire de fois.

Boucle while
Syntaxe
while (condition)
bloc
Une boucle while excute le bloc d'instructions tant que la condition est vrifie.

Exemple
int i = 0;
while (i < 10)
{
printf("%d\n", i);
i = i + 1;
}
Cet exemple est le mme que celui vu prcdement pour la boucle for, il affiche les dix
premiers entiers partir de 0.

39

Structures de contrle - itrations

Boucle do-while
Syntaxe
do
bloc
while (condition);
Cette boucle est une lgre variante de la prcdente. Elle excute donc le bloc au moins
une fois, et ce jusqu' ce que la condition soit fausse (ou tant que la condition est vraie).

Exemple 1
#include <stdio.h>
int main(void)
{
int i = 0;
do
{
printf("%d\n", i);
i = i + 1;
} while (i < 10);
return 0;
}
Exemple identique celui de la boucle while au dessus, on affiche les nombres de 0 jusqu'
9.

Exemple 2
#include <stdio.h>
int main(void)
{
char c;
do
{
printf("Veuillez taper la lettre 'o'\n");
scanf("%c", &c);
} while (c != 'o');
return 0;
}
Cette fois on voit l'utilit de la boucle do-while, la variable est initialise dans la boucle. On
doit tester sa valeur la fin de l'itration. Tant que l'utilisateur n'aura pas tap la lettre o,
le programme redemandera de taper la lettre.

40

Structures de contrle - itrations

Arrt et continuation des boucles


Il arrive frquemment qu'en valuant un test l'intrieur d'une boucle, on aimerait arrter
brutalement la boucle ou alors sauter certains cas non significatifs. C'est le rle des
instructions break et continue.
break permet de sortir immdiatement d'une boucle for, while ou do-while. noter
que si la boucle se trouve elle-mme dans une autre boucle, seule la boucle o
l'instruction break se trouvait est stoppe.
continue permet de recommencer la boucle depuis le dbut du bloc. Dans le cas de la
boucle for, le bloc li l'incrmentation sera excut, puis dans tous les cas, la condition
sera teste.

Saut inconditionnel (goto)


Cette instruction permet de continuer l'excution du programme un autre endroit, dans
la mme fonction. On l'utilise de la manire suivante :
goto label;
O label est un identificateur quelconque. Cet identificateur devra tre dfini quelque part
dans la mme fonction, avec la syntaxe suivante :
label:
Ce label peut tre mis n'importe quel endroit dans la fonction, y compris avant ou aprs le
goto, ou dans un autre bloc que l o sont utiliss la ou les instructions goto pointant vers
ce label. Les labels doivent tre uniques au sein d'une mme fonction, mais on peut les
rutiliser dans une autre fonction.
Un cas o l'emploi d'une instruction goto est tout fait justifi, par exemple, est pour
simuler le dclenchement d'une exception, pour sortir rapidement de plusieurs boucles
imbriques.
void boucle(void)
{
while (1)
{
struct Evenement_t * event;
attend_evenement();
while ((event = retire_evenement()))
{
switch (event->type) {
case FIN_TRAITEMENT:
goto fin;
/* ... */
}
}
}
fin:

41

Structures de contrle - itrations


/* ... */
}
Dans ce cas de figure, il serait possible d'utiliser aussi un boolen qui indique que le
traitement est termin, pour forcer la sortie des boucles. L'utilisation de goto dans cet
exemple ne nuisant pas la lisibilit du programme et tant tout aussi efficace, il est tout
fait lgitime de l'utiliser.
Il faut cependant noter que cette instruction est souvent considre comme viter. En
effet, elle provoque un saut dans le code, qui se voit beaucoup moins bien que les
structures de contrle comme if ou while, par exemple. De fait, la pratique a montr
qu'un abus de son utilisation rend un code source rapidement peu comprhensible, et
difficilement maintenable. L'article de Edsger W. Dijsktra Go to considered harmful [1] (en),
datant de 1968, est l'exemple le plus connu de cette critique, qui a donn naissance la
programmation structure.

Rfrences
[1] http:/ / www. acm. org/ classics/ oct95/

Fonctions et procdures
Dfinition
Le code suivant dfinit une fonction fonction renvoyant une valeur de type type_retour
et prenant N arguments, par1 de type type1, par2 de type type2, etc.
type_retour fonction(type1 par1, type2 par2, /* ..., */ typeN parN) {
/* Dclarations de variables ... */
/* Instructions ... */
}
L'excution d'une fonction se termine soit lorsque l'accolade fermante est atteinte, soit
lorsque le mot clef return est rencontr. La valeur renvoye par une fonction est donne
comme paramtre return. Une procdure est une fonction renvoyant void, dans ce cas
return est appel sans paramtre.
Les passages des arguments aux fonctions se font toujours par valeur. Si on veut modifier
la valeur d'un argument pass en paramtre une fonction, en dehors de cette mme
fonction, il faut utiliser des pointeurs.

Dclaration par prototype


Le prototype d'une fonction correspond simplement son en-tte (tout ce qui prcde la
premire accolade ouvrante). C'est--dire son nom, son type de retour et les types des
diffrents paramtres. Cela permet au compilateur de vrifier que la fonction est appele
avec le bon nombre de paramtres et surtout avec les bons types. La ligne suivante dclare
la fonction fonction, mais sans la dfinir :
type_retour nom_fonction(type1, type2, /* ..., */ typeN);

42

Fonctions et procdures
noter que les noms des paramtres peuvent tre omis et que la dclaration doit se
terminer par un point-virgule (;), sans quoi vous pourrez vous attendre une cascade
d'erreurs.

Absence des paramtres


Avant la normalisation par l'ANSI, il tait possible de faire une dclaration partielle d'une
fonction, en spcifiant son type de retour, mais pas ses paramtres:
int f();
Cette dclaration ne dit rien sur les ventuels paramtes de la fonction f, sur leur nombre
ou leur type, au contraire de :
int g(void);
qui prcise que la fonction g ne prend aucun argument.
Cette dclaration partielle laissait au compilateur le soin de complter la dclaration lors de
l'appel de la fonction, ou de sa dfinition. On perd donc un grand intrt des prototypes.
Mais cause de l'immense quantit de code existant qui se reposait sur ce comportement,
l'ANSI (puis le WG14) n'ont pas interdit de tels programmes, mais ont dclar ds le C90
que cette construction est obsolte.

valuation des arguments


noter que nulle part dans le langage n'est spcifi l'ordre d'valuation des arguments.
Il faut donc faire attention aux oprateurs ayant des effets de bords, notamment lorsqu'on
utilise la mme variable. Le code suivant est imprvisible et non portable, bien qu'un
compilateur ne gnrera probablement pas d'avertissement :
Ce code contient une erreur volontaire !
int fonction(int, int, int);
void test(void)
{
int a = 0;
int b = fonction(a++, a++, a++);
}
Suivant le compilateur employ, tous les cas de figures sont envisageables. La fonction peut
effectivement tre appele avec fonction(0, 0, 0) ou fonction(0, 1, 2) ou encore
fonction(2, 1, 0), ou le programme peut tout aussi bien s'arrter.
Un autre cas auquel il faut faire attention est l'appel de fonctions qui peuvent avoir des
effets de bord. Dans ce cas, le comportement n'est pas indfini, mais les fonctions peuvent
tre appeles dans n'importe quel ordre:
int fonction(int, int, int);
int g(void);
int h(int);
void test(void)

43

Fonctions et procdures
{
/* 3 appels de fonction en 1 instruction */
int b = fonction(5, g(), h(6));
}
Dans cet exemple, g() peut tre appele avant h(6), ou l'inverse. Si ces fonctions ont des
effets de bord (affichage de messages, modification de variables globales...), alors le
comportement du programme dpendra de l'ordre que le systme choisit. Dans ce cas, la
seule manire d'tre sr de l'ordre est de l'imposer, de la manire suivante:
int fonction(int, int, int);
int g(void);
int h(int);
void test(void)
{
/* 1 instruction par appel de fonction */
int a = g();
int b = h(6);
int c = fonction(5, a, b);
}

Nombre variable d'arguments


Une fonctionnalit assez utile est d'avoir une fonction avec un nombre variable
d'arguments, comme la fameuse fonction printf(). Pour cela, il suffit de dclarer le
prototype de la fonction de la manire suivante :

Dclaration
#include <stdarg.h>
void ma_fonction(type1 arg1, type2 arg2, ...)
{
}
Dans l'exemple ci-dessus, les points de suspension ne sont pas un abus d'criture, mais bel
et bien une notation C pour indiquer que la fonction accepte d'autres arguments. L'exemple
est limit deux arguments, mais il est bien sr possible d'en spcifier autant qu'on veut.
C'est dans l'unique but de ne pas rendre ambige la dclaration, qu'aucun abus d'criture
n'a t employ.
L'inclusion de l'en-tte <stdarg.h> n'est ncessaire que pour traiter les arguments
l'intrieur de la fonction. La premire remarque que l'on peut faire est qu'une fonction
nombre variable d'arguments contient au moins un paramtre fixe. En effet la dclaration
suivante est invalide :
Ce code contient une erreur volontaire !
void ma_fonction(...);

44

Fonctions et procdures

Accs aux arguments


Pour accder aux arguments situs aprs le dernier argument fixe, il faut utiliser certaines
fonctions (ou plutt macros) de l'en-tte <stdarg.h> :
void va_start (va_list ap, last);
type va_arg (va_list ap, type);
void va_end (va_list ap);
va_list est un type opaque dont on n'a pas se soucier. On commence par l'initialiser avec
va_start. Le paramtre last doit correspondre au nom du dernier argument fixe de la
fonction, ou alors tout bon compilateur retournera au moins un avertissement.
Vient ensuite la collecte minutieuse des arguments. Il faut bien comprendre qu' ce stade,
le langage n'offre aucun moyen de savoir comment sont structures les donnes (c'est
dire leur type). Il faut absolument dfinir une convention, laisse l'imagination du
programmeur, pour pouvoir extraire les donnes correctement.
Qui plus est, il faut tre extrmement vigilant lors de la rcupration des paramtres,
cause de la promotion des types entiers ou rels. En effet, les entiers sont
systmatiquement promus en int, sauf si la taille du type est plus grande, auquel cas le
type est inchang. Pour les rels, le type float est promu en double, alors que le type
long double est inchang. C'est pourquoi ce genre d'instruction n'a aucun sens dans une
fonction nombre variable d'arguments :
Ce code contient une erreur volontaire !
char caractere = va_arg(list, char);
Il faut obligatoirement rcuprer un entier de type char, comme tant un entier de type
int.

Exemple de convention
Un bon exemple de convention est la fonction printf() elle mme. Elle utilise un
spcificateur de format qui renseigne la fois le nombre d'arguments qu'on s'attend
trouver mais aussi le type de chacun. D'un autre cot, analyser un spcificateur de format
est relativement rbarbatif, et on n'a pas toujours besoin d'une artillerie aussi lourde.
Une autre faon de faire, relativement rpandue, est de ne passer que des couples (type,
objet), o type correspond un code reprsentant un type (une numration par exemple)
et objet le contenu de l'objet lui-mme (int, pointeur, double, etc.). On utilise alors un
code spcial (gnralement 0) pour indiquer la fin des arguments, ou alors un des
paramtres pour indiquer combien il y en a. Un petit exemple complet :
#include <stdio.h>
#include <stdarg.h>
enum {
TYPE_FIN, TYPE_ENTIER, TYPE_REEL, TYPE_CHAINE
};
void affiche(FILE * out, ...)
{

45

Fonctions et procdures
va_list list;
int
type;
va_start(list, out);
while ((type = va_arg(list, int)))
{
switch (type)
{
case TYPE_ENTIER: fprintf(out, "%d", va_arg(list,
int)); break;
case TYPE_REEL:
fprintf(out, "%g", va_arg(list,
double)); break;
case TYPE_CHAINE: fprintf(out, "%s", va_arg(list, char
*)); break;
}
}
fprintf(out, "\n");
va_end(list);
}
int main(int nb, char * argv[])
{
affiche(stdout, TYPE_CHAINE, "Le code ascii de 'a' est ",
TYPE_ENTIER, 'a', 0);
affiche(stderr, TYPE_CHAINE, "2 * 3 / 5 = ", TYPE_REEL, 2. * 3
/ 5, 0);
return 0;
}
L'inconvnient de ce genre d'approche est de ne pas oublier le marqueur de fin. Dans les
deux cas, il faut tre vigilant avec les conversions implicites, notamment dans le second
cas. noter que la conversion (transtypage) explicite des types en une taille infrieure
celle par dfaut (int pour les entiers ou double pour les rels) ne permet pas de
contourner la promotion implicite. Mme crit de la sorte:
affiche(stderr, TYPE_CHAINE, "2 * 3 / 5 = ", TYPE_REEL, (float) (2. * 3
/ 5), 0);
Le rsultat transmis au cinquime paramtre sera quand mme promu implicitement en
type double.

46

Fonctions et procdures

Fonction inline
Il s'agit d'une extension ISO C99, qui l'origine vient du C++. Ce mot cl doit se placer
avant le type de retour de la fonction. Il ne s'agit que d'une indication, le compilateur peut
ne pas honorer la demande, notamment si la fonction est rcursive. Dans une certaine
mesure, les fonctionnalits proposes par ce mot cl sont dj prises en charge par les
instructions du prprocesseur. Beaucoup prfreront passer par une macro,
essentiellement pour des raisons de compatibilit avec d'anciens compilateurs ne
supportant pas ce mot cl, et quand bien mme l'utilisation de macro est souvent trs
dlicat.
Le mot cl inline permet de s'affranchir des nombreux dfauts des macros, et de
rellement les utiliser comme une fonction normale, c'est dire surtout sans effets de bord.
noter qu'il est prfrable de classifier les fonctions inline de manire statique. Dans le
cas contraire, la fonction sera aussi dclare comme tant accessible de l'extrieur, et donc
dfinie comme une fonction normale.
En la dclarant static inline, un bon compilateur devrait supprimer toute trace de la
fonction et seulement la mettre in extenso aux endroits o elle est utilise. Ceci permettrait
la limite de dclarer la fonction dans un fichier en-tte, bien qu'il s'agisse d'une pratique
assez rare et donc viter. Exemple de dclaration d'une fonction inline statique :
static inline int max(int a, int b)
{
return (a > b) ? a : b;
}

La fonction main
Nous allons revenir ici sur la fonction main, prsente dans chaque programme. Cette
fonction est le point d'entre du programme. La norme dfinit deux prototypes, qui sont
donc portables:
int main(int argc, char * argv[]) { /* ... */ }
int main(void) { /* ... */ }
Le premier prototype est plus "gnral": il permet de rcuprer des paramtres au
programme. Le deuxime existe pour des raisons de simplicit, quand on ne veut pas traiter
ces arguments.
Si ces deux prototypes sont portables, une implmentation peut nanmoins dfinir un autre
prototype pour main, ou spcifier une autre fonction pour le dmarrage du programme.
Cependant ces cas sont plus rares (et souvent spcifiques du C embarqu).

Paramtres de ligne de commande


La fonction main prend deux paramtres qui permettent d'accder aux paramtres passs
au programme lors de son appel. Le premier, gnralement appel argc (argument count),
est le nombre de paramtres qui ont t passs au programme. Le second, argv (argument
vector), est la liste de ces paramtres. Les paramtres sont stocks sous forme de chane de
caractres, argv est donc un tableau de chanes de caractres, ou un pointeur sur un
pointeur sur char. argc correspond au nombre d'lments de ce tableau.

47

Fonctions et procdures
La premire chane de caractres, dont l'adresse est dans argv[0], contient le nom du
programme. Le premier paramtre est donc argv[1]. Le dernier lment du tableau,
argv[argc], est un pointeur nul.

Valeur de retour
La fonction main retourne toujours une valeur de type entier. L'usage veut qu'on retourne
0 (ou EXIT_SUCCESS) si le programme s'est droul correctement, ou EXIT_FAILURE pour
indiquer qu'il y a eu une erreur (Les macros EXIT_SUCCESS et EXIT_FAILURE tant dfinies
dans l'en-tte <stdlib.h>). Il est possible par le programme appelant de rcuprer ce code
de retour, et de l'interprter comme bon lui semble.

Exemple
Voici un petit programme trs simple qui affiche la liste des paramtres passs au
programme lorsqu'il est appel:
#include <stdio.h>
int main(int argc, char * argv[])
{
int i;
for (i = 0; i < argc; i++)
printf("paramtre %i : %s\n", i, argv[i]);
return 0;
}
On effectue une boucle sur argv l'aide de argc. Enregistrez-le sous le nom params.c puis
compilez-le (cc params.c -o params). Vous pouvez ensuite l'appeler ainsi:
./params hello world ! # sous Unix
params.exe hello world ! # sous MS-DOS ou Windows
Vous devriez voir en sortie quelque chose comme ceci (paramtre 0 varie selon le systme
d'exploitation):
paramtre
paramtre
paramtre
paramtre

0
1
2
3

:
:
:
:

./params
hello
world
!

48

Fonctions et procdures

Fonctions en C pr-ANSI
Absence de prototype lors de l'appel d'une fonction
Avant la normalisation C89, on pouvait appeler une fonction sans disposer ni de sa
dfinition, ni de sa dclaration. Dans ce cas, la fonction tait implicitement dclare comme
retournant le type int, et prenant un nombre indtermin de paramtres.
/* Aucune dclaration de g() n'est visible. */
void f(void)
{
g(); /* Dclaration implicite: extern int g() */
}
cause de la grande quantit de code existant l'poque qui reposait sur ce
comportement, le C90 a conserv cette possibilit. Cependant, elle a t retire de la
norme C99, et est viter mme lorsqu'on travaille en C90.
En effet, c'est plus qu'une bonne habitude de programmation de s'assurer que chaque
fonction utilise dans un programme ait son prototype dclar avant qu'elle ne soit dfinie
ou utilise. C'est d'autant plus indispensable lorsque les fonctions sont dfinies et utilises
dans des fichiers diffrents.

Ancienne notation
titre anecdotique, ceci est la faon historique de dfinir une fonction, avant que le
prototypage ne fut utilis. Cette notation est interdite depuis C90.
type_retour fonction(par1, par2, ..., parN)
type1 par1;
type2 par2;
...
typeN parN;
{
/* Dclarations de variables ... */
/* Instructions ... */
}
Au lieu de dclarer les types l'intrieur mme de la fonction, ils sont simplement dcrits
aprs la fonction et avant la premire accolade ouvrante. noter que type_retour pouvait
tre omis, et dans ce cas valait par dfaut int.

49

Tableaux

Tableaux
Il existe deux types de tableaux : les tableaux statiques, dont la taille est connue la
compilation, et les tableaux dynamiques, dont la taille est connue l'excution. Nous ne
nous intresserons pour l'instant qu'aux tableaux statiques, les tableaux dynamiques seront
prsents avec les pointeurs.

Syntaxe
Dclaration
T tableau[N];

Dclare un tableau de N lments, les lments tant de type T. N doit tre un nombre
connu la compilation, N ne peut pas tre une variable. Pour avoir des tableaux
dynamiques, il faut passer par l'allocation mmoire.
Cette dernire restriction a t leve par la norme C99 qui a apport le type tableau
longueur variable appels Variable Length Array (VLA). il est maintenant possible de
dclarer un tableau de type VLA par :
T tableau[expr];

o expr est une expression entire, calcule l'excution du programme. Ce type de


tableau ne peut pas appartenir la classe de stockage static ou extern. Sa porte est
limite au bloc dans lequel il est dfini. Par rapport un allocation dynamique, les risques
de fuite mmoire sont supprims.

Accs aux lments


L'exemple prcdent a permis de dclarer un tableau N lments. En C, ces lments
sont indexs de 0 N-1, et le ie lment peut tre accd de la manire suivante (o i est
suppos dclar comme une variable entire ayant une valeur entre 0 et N-1):
tableau[i]

Note: La syntaxe suivante est quivalente la prcdente:


i[tableau]

Bien qu'autorise par le C, elle va l'encontre de ce quoi nombre de programmeurs sont


habitus dans d'autres langages, c'est pourquoi elle est trs peu utilise. Comme, de plus,
elle n'apporte aucun rel avantage par rapport la premire syntaxe, elle est simplement
viter, au profit de la prcdente.

50

Tableaux

51

Exemples
int i;
int tableau[10]; /* dclare un tableau de 10 entiers */
for (i = 0; i < 10; i++) /* boucle << classique >> pour le parcours d'un tableau */
{
tableau[i] = i; /* chaque case du tableau reoit son indice comme
valeur */
}
Ce code contient une erreur volontaire !
int tableau[1]; /* dclare un tableau d'un entier */
tableau[10] = 5; /* accde l'lment d'indice 10 (qui ne devrait pas
exister) */
printf("%d\n", tableau[10]);
Ce deuxime exemple, non seulement peut compiler (le compilateur peut ne pas dtecter le
dpassement de capacit), mais peut aussi s'excuter et afficher le bon rsultat. Le
langage C n'impose pas une implmentation de vrifier les accs, en criture
comme en lecture, hors des limites d'un tableau; il prcise explicitement qu'un tel
code a un comportement indfini, donc que n'importe quoi peut se passer. En l'occurence,
ce code peut trs bien marcher comme on pourrait l'attendre, i.e. afficher 5, ou causer un
arrt du programme avec erreur (si la zone qui correspondrait au 11me lment du tableau
est hors de la mmoire alloue au processus, le systme d'exploitation peut dtecter une
tentative d'accs invalide une zone mmoire, ce qui peut se traduire par une erreur de
segmentation qui termine le programme), ou encore corrompre une autre partie de la
mmoire du processus (dans le cas o le pseudo-11me lment correspondrait une partie
de la mmoire du processus), ce qui peut modifier son comportement ultrieur de manire
trs difficile prvoir.
Ce code est donc un exemple d'un type de bogue trs courant en langage C. S'il est facile
dtecter dans ce code trs court, il peut tre trs difficile identifier dans du code plus
complexe, o on peut par exemple essayer d'accder un indice i d'un tableau, la valeur
de i pouvant varier suivant un flot d'excution complexe, ou alors essayer de copier le
contenu d'un tableau dans un autre, sans vrifier la taille du tableau de destination (ce
dernier cas est connu sous le nom de dbordement de tampon, dpasement de capacit, ou
encore buffer overflow en anglais). Il est donc trs important de s'assurer que tous les
accs au contenu de tableaux, en lecture comme en criture, se font dans les
limites de ses bornes.

Tableaux

52

Tableaux plusieurs dimensions


Les tableaux vus pour l'instant taient des tableaux une dimension (ie : des tableaux un
seul indice), il est possible de dclarer des tableaux possdant un nombre aussi grand que
l'on veut de dimensions. Par exemple pour dclarer un tableau d'entiers deux dimensions
:
int matrice[10][5];
Pour accder aux lments d'un tel tableau, on utilise une notation similaire celle vue
pour les tableaux une dimension :
matrice[0][0] = 5;
affecte la valeur 5 la case d'indice (0,0) du tableau.
Avec le type VLA (Variable Length Array), introduit par C99, il est possible de dfinir un
tableau plusieurs dimensions dont les bornes ne sont connues qu' l'excution et non la
compilation :
static void vlaDemo(int taille1, int taille2)
{
// Declaration du tableau VLA
int table[taille1][taille2];
// ...

Initialisation des tableaux


Il est possible d'initialiser directement les tableaux lors de leur dclaration :
int tableau[5] = { 1 , 5 , 45 , 3 , 9 };
initialise le tableau d'entiers avec les valeurs fournies entre accolades (tableau[0] = 1;,
tableau[1] = 5;, etc.)
noter que si on ne spcifie aucune taille entre les crochets du tableau, le compilateur la
calculera automatiquement pour contenir tous les lments. La dclaration ci-dessus aurait
pu s'crire plus simplement :
int tableau[] = { 1 , 5 , 45 , 3 , 9 };
Si on dclare un tableau de taille fixe, mais qu'on l'initialise avec moins d'lments qu'il
peut contenir, les lments restant seront mis zro. On utilise cette astuce pour initialiser
rapidement un tableau zro :
int tableau[512] = {0};
Cette technique est nanmoins viter, car dans ce cas le tableau sera stock en entier
dans le code du programme, faisant grossir artificiellement la taille du programme
excutable. Alors qu'en ne dclarant qu'une taille et l'initialisant la main au dbut du
programme, la plupart des formats d'excutable sont capables d'optimiser en ne stockant
que la taille du tableau dans le programme final.
Neanmoins, cette syntaxe est aussi utilisable pour les tableaux plusieurs dimensions :
int matrice[2][3] = { { 1 , 2 , 3 } , { 4 , 5 , 6 } };

Tableaux
Ce qui peut aussi s'crire :
int matrice[2][3] = { 1 , 2 , 3 , 4 , 5 , 6 };
noter que si on veut aussi utiliser l'adaptation dynamique, il faut quand mme spcifier
une dimension :
int identite[][3] = { { 1 , 0 , 0 } , { 0 , 1 , 0 } , { 0 , 0 , 1 } };
/* Ou plus "simplement" */
int identite[][3] = { { 1 } , { 0 , 1 } , { 0 , 0 , 1 } };

Conversion des noms de tableaux en pointeurs


A quelques exceptions prs (c.f. ci-dessous), un nom de tableau apparaissant dans une
expression C sera automatiquement converti en un pointeur vers son premier lment lors
de l'valuation de cette expression : si tab est le nom d'un tableau d'entiers, le nom de
tableau tab sera converti dans le type pointeur vers int dans l'expression tab, et la valeur
de cette expression sera l'adresse du dbut de la zone mmoire alloue pour le stockage
des lments du tableau, i.e. l'adresse de son premier lment. Le code suivant est par
exemple valide :
int
int
p =
q =

tab[10];
*p, *q;
tab;
&(tab[0]);

La dclaration int tab[10] rserve une zone mmoire suffisante pour stocker 10 variables
de type int, dsignes par tab[0], tab[1]..., tab[9]. Dans l'instruction p = tab,
l'expression tab est de type pointeur vers int, et la valeur affecte p est l'adresse de la
premire case du tableau. Cette adresse est aussi l'adresse de la variable tab[0] : dans la
seconde instruction, l'adresse de tab[0] est affecte q via l'oprateur de prise d'adresse
&. Aprs la seconde instruction, p et q ont la mme valeur, et les instructions *p = 1, *q =
1 ou tab[0] = 1 deviennent opratoirement quivalentes.

Exceptions la rgle de conversion


Noter que dans la dclaration int tab[10], le nom tab ne dsigne pas en lui-mme un
pointeur mais bien un tableau, de type tableau d'entiers 10 lments, mme si ce nom est
converti en pointeur dans la plupart des contextes. Les exceptions la rgle de conversion
interviennent :

lorsque le nom du tableau est oprande de l'oprateur unaire &,


lorsqu'il est argument de l'oprateur sizeof,
lorsqu'il est argument d'un pr ou d'une post-incrmentation ou dcrmentation (--,++)
lorsqu'il constitue la partie gauche d'une affectation,
lorsqu'il est suivi de l'oprateur d'accs un champ.

Dans chaque cas, le nom du tableau gardera le sens d'une variable de type tableau,
c'est--dire conservera son type initial :
dans l'expression &tab, la sous-expression tab conserve son type "tableau d'entiers 10
lments", et l'expression elle-mme est de type "pointeur vers tableaux d'entiers 10
lments". Le code suivant est par exemple valide :

53

Tableaux

54

int tab[10]; /* tableau d'entiers 10 lments */


int (*p)[10]; /* pointeur vers les tableaux d'entiers 10 lments */
p = &tab;
Le code suivant est en revanche incorrect :
Ce code contient plusieurs erreurs volontaires !
int tab[10];
int *p;
p = &tab; /* incorrect. '&tab' n'est pas du bon type

*/

dans sizeof tab la taille calcule sera celle de tab considr encore comme une
variable de type "tableau d'entiers 10 lments", soit 10 fois la taille d'un int.
dans tab++ , tab-- , --tab ou ++tab , la sous-expression tab n'est ni une expression
numrique ni un pointeur, mais bien une variable de type tableau : elle n'est donc ni
incrmentable, ni dcrmentable, et l'expression globale ne sera jamais compilable,
dans l'instruction tab = expr o expr est une expression quelconque, tab est toujours
considr comme une variable de type "tableau d'entiers 10 lments"... la rgle de
conversion des noms de tableaux et les restrictions sur les conversion explicites font qu'il
est impossible que expr soit de mme type : cette affectation gnrera toujours une
erreur de typage, et ne sera jamais compilable,
dans l'expression tab.champ o champ est un nom de champ quelconque, tab est de
type tableau, i.e. ne possde aucun champ, donc cette expression ne sera jamais
compilable.
Le cas o tab est membre gauche d'une affectation montre que les valeurs de type tableau
ne sont jamais manipulables de manire directe : il est par exemple impossible d'affecter un
tableau un autre tableau - l'instruction tab_1 = tab_2 est toujours incompilable, cause
de la conversion en pointeur de tab_2 - ou de comparer structurellement deux tableaux par
une comparaison simple - dans tab_1 == tab_2 , les deux noms seront convertis en
pointeur, et la comparaison effectue sera celle des adresses.

Cas des paramtres de fonctions


Tout paramtre de fonction dclar comme tant de type "tableau d'lements de type T" est
automatiquement converti en un pointeur vers T la compilation. Les deux critures
suivantes sont quivalentes :
version avec tableaux

void f(int t[])


{
/* ... */
}
void g(int m[][10])
{
/* ... */
}

version avec pointeurs

void f(int *t)


{
/* ... */
}
void g(int (*m)[10])
{
/* ... */
}

Dans la premire version de f et g , les paramtres t et m sont bien des pointeurs,


conformment l'quivalence avec la seconde version, et sont donc raffectables dans le
corps de ces fonctions. La taille en la premire dimension de t et de m , mme si elle est
spcifie dans le code (e.g. void g(int m[10][10]) ), sera de toute manire efface la

Tableaux

55

compilation : il ne s'agit alors que d'un simple commentaire pour le programmeur.


Cette quivalence est cohrente avec la rgle de conversion des noms de tableaux : lors
d'un appel de la forme f(tab) o tab est un tableau d'entiers, le nom tab sera converti en
pointeur vers int , qui est bien le type du paramtre de f dans la seconde version.

Cas des tableaux externes


Dans le cas des dclarations externes, et contrairement au cas des paramtres de fonctions,
il n'y a pas quivalence entre la notation par tableaux et la notation par pointeurs. Le
programme suivant est en gnral compilable, mais incorrect :
Ce code contient une erreur volontaire !
fichier1.c

#include <stdio.h>
int tab[] = {42};
void f(void)
{
printf("fichier1 : tab = %p\n",
tab);
}

fichier2.c

#include <stdio.h>
extern int *tab;
void f(void)
int main(int nb, char *argv[] )
{
f();
printf("fichier2 : tab = %p\n",
tab);
return 0;
}

Les valeurs affiches pour tab dans main et dans f seront en gnral distinctes. L'erreur
se situe dans la dclaration externe de tab situe dans le fichier fichier2.c. Il aurait fallu
crire la dclaration suivante, spcifiant correctement le type de tab dans fichier2.c
comme tant celui d'un tableau et non d'un pointeur (mais ne spcifiant pas sa taille, le
compilateur n'effectuant de toute manire aucun test de dpassement) :
extern int tab[];
L'absence de message d'erreur dans la compilation de la premire version de ce
programme n'est due qu' l'insuffisance de la vrification de la cohrence globale du typage
lors de la phase de liaison : il est plus gnralement possible de dclarer une variable
externe de n'importe quel autre type que son type rel, sans que l'incohrence globale du
typage soit ncessairement signale par le compilateur. Aucune norme ne spcifiant le
comportement du programme rsultant, ce comportement est, de fait, indfini.

Tableaux VLA passs une fonction


En C99, il est possible de passer une fonction un tableau de type VLA (Variable Length
Array). Il faut passer les tailles des dimensions en premier.
Exemple de fonction recevant en argument un tableau de type VLA deux dimensions :
static void vlaPrint(int taille1, int taille2, int
table[taille1][taille2])
{
// Ecriture du tableau sur stdout
for (int i=0; i< taille1; i++)
{

Tableaux
for (int j=0; j< taille2; j++)
{
(void)printf("table[%d][%d] = %d\n", i, j,
table[i][j]);
}
}
}

Chanes de caractres
Comme il a t dit tout au long de cet ouvrage, les chanes de caractres sont des tableaux
particuliers. En dclarant une chane de caractres on peut soit la manipuler en tant que
pointeur soit en tant que tableau. Considrez les dclarations suivantes :
char * chaine1 = "Ceci est une chaine";
char chaine2[] = "Ceci est une autre chaine";
Bien que se manipulant exactement de la mme faon, les oprations permises sur les deux
variables ne sont pas tout fait les mmes. Dans le premier cas on dclare un pointeur sur
une chane de caractres statique, dans le second cas, un tableau (allou soit sur la pile si
la variable est dclare dans une fonction ou soit dans le segment global si la variable est
globale) de taille suffisante pour contenir tous les caractres de la chane affecte (incluant
le caractre nul).
Le second cas est donc une notation abrge pour char chaine2[] = { 'C', 'e', 'c',
'i', ' ', ..., 'e', 0 };. Dans tous les autres cas (ceux o une chane de caractres ne
sert pas initialiser un tableau), la chane dclare est statique (les donnes sont
persistantes entre les diffrents appels de fonctions), et sur certaines architectures, pour
ne pas dire toutes, elle est mme en lecture seule. En effet l'instruction chaine1[0] =
'c'; peut provoquer un accs illgal la mmoire. En fait le compilateur peut optimiser la
gestion des chanes en regroupant celles qui sont identiques. C'est pourquoi il est
prfrable de classifier les pointeurs sur chane de caractres avec le mot cl const.
On comprends aisment que si chaine2 est allou sur la pile (dclar dans une fonction), la
valeur ne pourra pas tre utilise comme valeur de retour. Tandis que la valeur de
chaine1 pourra tre retourne mme si c'est une variable locale.

56

Pointeurs

Pointeurs
Dans cette section, nous allons prsenter un mcanisme permettant de manipuler les
adresses, les pointeurs. Un pointeur a pour valeur l'adresse d'un objet C d'un type donn
(un pointeur est typ). Ainsi, un pointeur contenant l'adresse d'un entier sera de type
pointeur vers entier.

Usage et dclaration
L'oprateur & permet de connaitre l'adresse d'une variable, on dira aussi la rfrence.
Toute dclaration de variable occupe un certain espace dans la mmoire de l'ordinateur. La
rfrence permet de savoir o cet emplacement se trouve. En simplifiant l'extrme, on
peut considrer la mmoire d'un ordinateur comme une gigantesque table d'octets. Quand
on dclare une variable de type int, elle sera alloue un certain emplacement (ou dit
autrement : un indice, une adresse ou une rfrence) dans cette table. Un pointeur permet
simplement de stocker une rfrence, il peut donc tre vu comme un nombre allant de 0
la quantit maximale de mmoire dont dispose votre ordinateur (moins un, pour tre exact).
Un pointeur occupera habituellement toujours la mme taille (occupera la mme place en
mmoire), quelque soit l'objet se trouvant cet emplacement. Il s'agit en gnral de la plus
grande taille directement grable par le processeur : sur une architecture 32bits, elle sera
de 4 octets, sur une architecture 64bits, 8 octets, etc. Le type du pointeur ne sert qu'
renseigner comment sont organises les donnes suivant l'adresse rfrence par le
pointeur. Ce code, par exemple, affiche la rfrence d'une variable au format hexadcimal:
int i;
printf("%p\n", &i);
Pouvoir rcuprer l'adresse n'a d'intrt que si on peut manipuler l'objet point. Pour cela,
il est ncessaire de pouvoir dclarer des pointeurs, ou dit autrement un objet pouvant
contenir des rfrences. Pour cela on utilise l'toile (*) entre le type et le nom de la variable
pour indiquer qu'il s'agit d'un pointeur:
T * pointeur, * pointeur2, /* ..., */ * pointeurN;
Dclare les variables pointeur, pointeur2, ..., pointeurN de type pointeur vers le type T.
noter la bizarrerie du langage vouloir associer l'toile la variable et non au type, qui
oblige rpter l'toile pour chaque variable.
/* Ce code contient une dclaration volontairement confuse */
int * pointeur, variable;
Cet exemple de code dclare un pointeur sur un entier de type int et une variable de type
int. Dans un vrai programme, il est rarement possible d'utiliser des noms aussi triviaux,
aussi il est recommand de sparer la dclaration des variables de celles des pointeurs (ou
d'utiliser l'instruction typedef, qui, elle, permet d'associer l'toile au type), la lisibilit du
programme sera lgrement amliore.
Il est essentiel de bien comprendre ce qui a t dclar dans ces exemples. Chaque
pointeur peut contenir une rfrence sur un emplacement de la mmoire (un indice dans
notre fameuse table). On peut obtenir une rfrence (ou un indice) avec l'oprateur & (ou

57

Pointeurs
allouer une rfrence soit-mme avec des fonctions ddies, c.f la section suivante). Cet
oprateur transforme donc une variable de type T en un pointeur de type T *. Insistons sur
le terme variable, car videmment des expressions telles que '&23435' ou '&(2 * a / 3.)'
n'ont aucun sens, dans la mesure o les constantes et expressions du langage n'occupent
aucun emplacement susceptible d'intresser votre programme.
Ce code, par exemple, affiche la rfrence d'une variable dans un format dfini par
l'implmentation (qui peut tre hexadcimal, ou une combinaison "segment:offset", par
exemple):
int i;
printf("%p\n", &i);
Il ne faut pas oublier que, comme toutes les variables locales en C, un pointeur est
l'origine non initialis. Une bonne attitude de programmation est de s'assurer que lorsqu'il
ne pointe pas vers un objet valide, sa valeur est mise zro (ou NULL, qui est dclar entre
autre dans stdio.h).

L'arithmtique des pointeurs


L'arithmtique associe aux pointeurs est sans doute ce qui a valu au C sa rputation
d'assembleur plus compliqu et plus lent que l'assembleur . On peut trs vite construire
des expressions incomprhensibles avec les oprateurs disponibles. Dans la mesure du
possible, il est conseill de se limiter des expressions simples, quitte les dcomposer,
car la plupart des compilateurs savent trs bien optimiser un code C.

Drfrencement
Il s'agit de l'opration la plus simple sur les pointeurs. Comme son nom l'indique, il s'agit de
l'opration rciproque au rfrencement (&). L'oprateur associ est l'toile (*), qui est
aussi utilis pour dclarer un type pointeur. Cet oprateur permet donc de transformer un
pointeur de type T *, en un objet de type T, les oprations affectant l'objet point :
int variable = 10;
int * pointeur = &variable;
*pointeur = 20; /* Positionne 'variable' 20 */
Ici, pointeur contient une adresse valide, celle de variable; son drfrencement est
donc possible. Par contre, si pointeur tait une variable locale non initialise, son
drfrencement provoquerait coup sr un arrt brutal de votre programme.
Vous obtiendrez le mme rsultat, si pointeur est initialis NULL. Cette adresse est
invalide et toute tentative de drfrencement se soldera par un arrt du programme.

58

Pointeurs

59

Arithmtique de base
L'arithmtique des pointeurs s'apparente celle des entiers, mais il est important de
comprendre la distinction entre ces deux concepts.
Les oprations arithmtiques permises avec les pointeurs sont:
Addition / soustraction d'une valeur entire un pointeur (on avance / recule d'un
nombre de cases mmoires gal la taille du type T) : le rsultat est donc un pointeur, de
mme type que le pointeur de dpart.
Il faut bien faire attention avec ce genre d'opration ne pas sortir du bloc mmoire, car le
C n'effectuera aucun test pour vous. Considrez l'exemple suivant:
/* Parcours les lments d'un tableau */
int tableau[N];
int * p;
for (p = tableau; p < &tableau[N]; p ++)
{
/* ... */
}
Normalement un tableau de N cases permet d'tre itr sur les indices allant de 0 N - 1,
inclusivement. L'expression &tableau[N] fait rfrence la case mmoire non alloue
immdiatement aprs le plus grand indice, donc potentiellement source de problme.
Toutefois, par exception pour le premier indice aprs le plus grand, C garantit que le
rsultat de l'expression soit bien dfini. Bien sr, il ne faut pas drfrencer ce pointeur.
noter qu' l'issue de la boucle, p pointera sur la N+1me case du tableau, donc hors de
l'espace allou. Le C autorise tout fait ce genre de pratique, il faut juste faire attention
ne pas dfrencer le pointeur cet endroit.
Soustraction de deux pointeurs de mme type (combien d'objet de type T y a t-il entre les
deux pointeurs) : le rsultat est donc un entier, de type ptrdiff_t.
Ce code contient
une erreur volontaire !
int
autre_tableau[3];
int
tableau[10];
int *
p
= &tableau[5];
tableau */
int *
q
= &tableau[3];
tableau */
ptrdiff_t diff1 = p - q;
ptrdiff_t diff2 = q - p;

/* p pointe sur le 6e lment du


/* q pointe sur le 4e lment du
/* diff1 vaut 2 */
/* diff2 vaut -2 */

q = &autre_tableau[2];
ptrdiff_t dif3 = p - q; /* Erreur ! */
Dans cet exemple, les deux premires soustractions sont dfinies, car p et q pointent sur
des lments du mme tableau. La troisime soustraction est indfinie, car on utilise des
adresses d'lments de tableaux diffrents.
Notons que l'oprateur [] s'applique toujours une oprande de type entier et une autre
de type pointeur. Lorsqu'on crit tableau[i], il y a en fait une conversion de tableau

Pointeurs
pointeur avec l'application de l'oprateur []. On peut donc bien sr utiliser l'oprateur []
avec un pointeur pour oprande:
int
a;
int b;
int * p = &a; /* On peut accder la valeur de 'a' via 'p[0]' ou '*p'
*/
/* p[1] est indfini - n'esprez pas accder la valeur de b depuis
l'adresse de a */

Arithmtique avec effet de bord


C'est sans doute ce qui a donn des sueurs froides des gnrations de programmeurs
dcouvrant le C : un usage optimis de la priorit des oprateurs, le tout imbriqu dans
des expressions rallonge. Par exemple 'while( *d++ = *s++ );', pour copier une chaine
de caractres.
En fait, en dcomposant l'instruction, c'est nettement plus simple qu'il ne parait. Par
exemple :
int i;
int * entier;
/* ... */
i = *entier++; /* i = *(entier++); */
Dans ce cas de figure, l'oprateur d'incrmentation ayant priorit sur celui de
drfrencement, c'est celui-ci qui sera appliqu en premier. Comme il est postfix,
l'oprateur ne prendra effet qu' la fin de l'expression (donc de l'affectation). La variable i
sera donc tout simplement affecte de la valeur pointe par entier et aprs cela le pointeur
sera incrment. Voici les diffrents effets suivant les combinaisons de ces deux oprateurs
:
i = *++entier; /* Incrmente d'abord le pointeur, puis drfrence la
nouvelle adresse pointe */
i = ++*entier; /* Incrmente la valeur pointe par "entier", puis
affecte le rsultat "i" */
i = (*entier)++; /* Affecte la valeur pointe par "entier" et
incrmente cette valeur */
On peut videmment complexifier les expressions outrance, mais privilgier la compacit
au dtriment de la clart et de la simplicit dans un hypothtique espoir d'optimisation est
une erreur de dbutant viter.

60

Pointeurs

Tableaux dynamiques
Un des intrts des pointeurs et de l'allocation dynamique est de permettre de dcider de la
taille d'une variable au moment de l'excution, comme par exemple pour les tableaux. Ainsi
pour allouer un tableau de n entiers (n tant connu l'excution), on dclare une variable
de type pointeur sur entier laquelle on alloue une zone mmoire correspondant n
entiers :
int * alloue_tableau(int n, size_t taille)
{
return malloc(n * taille);
}
/* Ailleurs dans le programme */
int * tableau = alloue_tableau(256, sizeof *tableau);
if (tableau != NULL)
{
/* oprations sur le tableau */
/* ... */
free( tableau );
}
Cet exemple alloue un tableau de 256 cases. Bien que la variable soit un pointeur, il est
dans ce cas permis d'accder aux cases de 0 255, soit entre les adresses &tableau[0] et
&tableau[255], incluses.

Tableaux dynamiques plusieurs dimensions


Tout comme on pouvait dclarer des tableaux statiques plusieurs dimensions, on peut
dclarer des tableaux dynamiques plusieurs dimensions. Pour dclarer un tel tableau, on
dclare des pointeurs sur des pointeurs (etc.) sur des types. Pour dclarer un tableau
dynamique d'entiers deux dimensions :
int ** matrice;
L'allocation d'un tel objet va se drouler en plusieurs tapes (une par toile), on alloue
d'abord l'espace pour un tableau de pointeurs vers entier. Ensuite, on alloue pour chacun
de ces tableaux l'espace pour un tableau d'entiers. Si on veut une matrice 4x5 :
#define LIGNES 4
#define COLONNES 5
int i;
matrice = malloc(sizeof *matrice * LIGNES);
for (i = 0; i < LIGNES; i++)
{
matrice[i] = malloc(sizeof **matrice * COLONNES);
}
Pour librer l'espace allou pour une telle structure, on procde de manire inverse, on
commence par librer chacune des lignes du tableau, puis le tableau lui mme :

61

Pointeurs
for(i = 0; i < LIGNES; i++)
{
free(matrice[i]);
}
free(matrice);

Utilisation des pointeurs pour passer des paramtres par


adresse
Toutes les variables en C, l'exception des tableaux, sont passs par valeurs aux
paramtres des fonctions. C'est dire qu'une copie est effectue sur la pile d'appel. Si bien
que toutes les modifications de la variable effectues dans la fonction seront perdues une
fois de retour l'appelant. Or, il y a des cas o l'on aimerait bien pouvoir modifier une
variable passe en paramtre et que ces modifications perdurent dans la fonction
appelante. C'est un des usages des paramtres par adresse : permettre la modification
d'une variable de l'appelant, comme dans l'exemple suivant:
#include <stdio.h>
/* Ce code change le contenu de deux variables */
void inverse(int * a, int *b)
{
int tmp;
tmp = *a;
*a = *b;
*b = tmp;
}
int main(void)
{
int a = 5;
int b = 2;
printf("a = %d, b = %d.\n", a, b);
/* On passe 'inverse' les adresses de a et b. */
inverse(&a, &b);
printf("a = %d, b = %d.\n", a, b);
inverse(&a, &b);
printf("a = %d, b = %d.\n", a, b);
return 0;
}
Ce passage par adresse est extrmement rpandu pour optimiser la quantit de donnes
qui doit transiter sur la pile d'appel (qui est, sur beaucoup de systmes, de taille fixe). En
fait, mme si la variable ne doit pas tre modifie, on utilise quand mme un passage par

62

Pointeurs
adresse, juste pour viter la copie implicite des variables autres que les tableaux. Ceci est
particulirement intressant avec les structures, puisque celles-ci ont tendance tre assez
imposantes, et cela ne nuit pas trop la lisibilit du programme.

Pointeurs vers fonctions


Les pointeurs vers les fonctions sont un peu spciaux, parce qu'ils n'ont pas d'arithmtique
associe (car une telle arithmtique n'aurait pas beaucoup de sens). Les oprations
permises avec les pointeurs sur fonctions sont en fait relativement limites:
type_retour (*pointeur_fonction)(liste_paramtres);
Dclare pointeur_fonction, un pointeur vers une fonction prenant liste_paramtres comme
paramtres et renvoyant type_retour. Le parenthsage est ici obligatoire, sans quoi l'toile
se rattacherait au type de retour. Pour faire pointer un pointeur vers une fonction, on
utilise une affectation normale :
pointeur_fonction = &fonction;
/* Qui est en fait quivalent : */
pointeur_fonction = fonction;
O fonction est compatible avec le pointeur (mmes paramtres et valeur de retour). Une
fois que le pointeur pointe vers une fonction, on peut appeler cette fonction :
(*pointeur_fonction)(paramtres);
/* Ou plus simplement, mais moins logique syntaxiquement */
pointeur_fonction(paramtres);

63

Types avancs - structures, unions, numrations

Types avancs -structures, unions,


numrations
Structures
struct ma_structure {
type1 champ1;
type2 champ2;
...
typeN champN;
} var1, var2, ..., varM;
Dclare une structure (ou enregistrement) ma_structure compos de N champs, champ1 de
type type1, champ2 de type type2, etc. On dclare, par la mme occasion, M variables de
type struct ma_structure.

Accs aux champs


L'accs aux champs d'une structure se fait avec un point :
struct complexe {
int reel;
int imaginaire;
} c;
c.reel = 1;
c.imaginaire = 2;

Initialisation
Il y a plusieurs faons d'initialiser une variable de type structure :
En initialisant les champs un un :
struct t_mastruct {
char ch;
int nb;
double pi;
};
t_mastruct variable;
variable.ch = 'a';
variable.nb = 12345;
variable.pi = 3.141592;
Cette faon est nanmoins pnible lorsqu'il y a beaucoup de champs.
la dclaration de la variable :
struct t_mastruct {
char ch;
int nb;
float pi;

64

Types avancs - structures, unions, numrations


};
t_mastruct variable = { 'a', 12345, 0.141592 };
Les valeurs des champs sont assigns dans l'ordre o ils sont dclars. S'il manque des
initialisations, les champs seront initialiss 0. L'inconvnient, c'est qu'on doit connaitre
l'ordre o sont dclars les champs, ce qui peut tre tout aussi pnible retrouver, et peut
causer des plantages lorsque la dfinition de la structure est modifie.
Une nouveaut du C99 permet d'initialiser certains champs la dclaration de la
variable, en les nommant :
struct t_mastruct {
char ch;
int nb;
float pi;
};
t_mastruct variable = { .pi = 3.141592, .ch = 'a', .nb = 12345 };
Les champs non initialiss seront mis leur valeur par dfaut.

Manipulation
La seule opration prise en charge par le langage est la copie, lors des affectations ou des
passages de paramtres des fontions. Toutes les autres oprations sont la charge du
programmeur, notamment la comparaison d'galit (cf. section suivante).

Alignement et bourrage (padding)


Il s'agit d'un concept relativement avanc, mais qu'il est bien de connaitre pour agir en
connaissance de cause. Lorsqu'on dclare une structure, on pourrait navement croire que
les champs se suivent les uns la suite des autres en mmoire. Or, il arrive souvent que des
octets soient intercals entre les champs.
Considrez la structure suivante:
struct ma_structure {
char champ1;
int
champ2;
char champ3;
};

/* 8 bits */
/* 32 bits */
/* 8 bits */

On pourrait penser que cette structure occupera 6 octets en mmoire, et pourtant, sur une
bonne partie des compilateurs, on obtiendrait une taille plus proche des 12 octets.
En fait, les compilateurs insrent quasiment toujours des octets entre les champs pour
pouvoir les aligner sur des adresses qui correspondent des mots machines. Cela est d
une limitation de la plupart des processeurs, qui ne peuvent lire des mots de plus d'un
octet que s'ils sont aligns sur un certain adressage (alors qu'il n'y a pas de contrainte
particulire pour lire un seul octet, si le type char est dfini sur 8bit).
En se reprsentant la mmoire comme un tableau continu, on peut tracer le dessin suivant:
| bloc N
| bloc N + 1
| bloc N + 2
|
---+---------------+---------------+---------------+--| a | b | c | d | e | f | g | h | i | j | k | l |

65

Types avancs - structures, unions, numrations


---+---------------+---------------+---------------+--Les cases a, b, c, ... reprsentent des octets, et les blocs des sections de 32 bits. Si on
suppose qu'une variable de type ma_structure doive tre place en mmoire partir du
bloc numro N, alors un compilateur pourra, pour des raisons de performance, placer
champ1 en a, champ2 de e h, et champ3 en i. Cela permettrait en effet d'accder
simplement champ2: le processeur fournit des instructions permettant de lire ou d'crire
directement le bloc N + 1. Dans ce cas, les octets de b d ne sont pas utiliss; on dit alors
que ce sont des octets de bourrage (ou padding en anglais). Un autre compilateur (ou le
mme, appel avec des options diffrentes) peut aussi placer champ2 de b e, et champ3
en f, pour optimiser l'utilisation mmoire. Mais alors il devra gnrer un code plus
complexe lors des accs champ2, le matriel ne lui permettant pas d'accder en une seule
instruction aux 4 octets b e.
En fait il faut garder l'esprit que toutes les variables suivent cette contrainte: aussi bien
les variables locales aux fonctions, les champs de structures, les paramtres de fonctions,
etc.
L'existence d'octets de bourrage ainsi que leur nombre sont non seulement dpendants de
l'architecture, mais aussi du compilateur. Cela dit, il est toujours possible de connatre la
distance (offset) d'un champ par rapport au dbut de la structure, et ce, de manire
portable. Pour cela il existe une macro, dclare dans l'entte stddef.h:
size_t offsetof(type, champ);
La valeur renvoye est le nombre de char (i.e. d'octets la plupart du temps), entre le dbut
de la structure et celui du champ. Le premier argument attendu est bel et bien le type de la
structure et non une variable de ce type, ni un pointeur vers une variable de ce type. Pour
s'en souvenir, il suffit de savoir que beaucoup d'implmentations d'offsetof utilisent une
arithmtique de ce genre:
size_t distance = (size_t) &((type *)NULL)->champ;
Si type tait un pointeur, il faudrait faire un drfrencement supplmentaire (ou viter
l'toile dans la macro). noter que, mme si cette macro peut s'avrer contraignante
(notamment lorsqu'on ne dispose que de type pointeur), il est quand mme prfrable de
l'utiliser pour des raisons de portabilit.
Voici un petit exemple d'utilisation de cette macro:
#include <stddef.h>
#include <stdio.h>
struct ma_structure {
char champ1;
int
champ2;
char champ3;
};
int main(void)
{
/* en C99 */
/* printf("L'offset de 'champ2' vaut %zu.\n", offsetof(struct

66

Types avancs - structures, unions, numrations


ma_structure, champ2)); */
/* en C90 */
printf("L'offset de 'champ2' vaut %lu.\n", (unsigned long)
offsetof(struct ma_structure, champ2));
return 0;
}
Sur une architecture 32 bits, vous obtiendrez trs certainement la rponse:
L'offset de 'champ2' vaut 4.
En fait toute cette section tait pour souligner le fait qu'il est difficilement portable de
comparer les structures comme des blocs binaires (via la fonction memcmp par exemple), car
il n'y a aucune garantie que ces octets de bourrage soient intialiss une certaine valeur.
De la mme manire, il est sage de prendre quelques prcautions avant de transfrer cette
structure l'extrieur du programme (comme un fichier, un tube de communication ou une
socket IP). En gnral, il prfrable de traiter la structure champ par champ, pour ce genre
d'oprations.

Pointeurs vers structures


Il est (bien entendu) possible de dclarer des variables de type pointeur vers structure :
struct ma_struct * ma_variable;
Comme pour tout pointeur, on doit allouer de la mmoire pour la variable avant de l'utiliser
:
ma_variable = malloc( sizeof(struct ma_struct) );
L'accs aux champs peut se faire comme pour une variable de type structure normale :
(* ma_variable).champ
Ce cas de figure est en fait tellement frquent qu'il existe un raccourci pour l'accs aux
champs d'un pointeur vers structure :
ma_variable->champ

Unions
Une union et un enregistrement se dclarent de manire identique :
union type_union
{
type1 champ1;
type2 champ2;
/* ... */
typeN champN;
};
/* Dclaration de variables */
union type_union var1, var2, /* ... */ varM;

67

Types avancs - structures, unions, numrations


Toutefois, la diffrence d'un enregistrement, les N champs d'une instance de cette union
occupent le mme emplacement en mmoire. Modifier l'un des champ modifie donc tous les
champs de l'union. Typiquement, une union s'utilise lorsqu'un enregistrement peut occuper
plusieurs fonctions bien distinctes et que chaque fonction ne requiert pas l'utilisation de
tous les champs.
Voici un exemple ultra classique d'utilisation des unions, qui provient en fait de la gestion
des vnements du systme X-Window, trs rpandu sur les systmes Unix:
union _XEvent
{
int type;
XAnyEvent xany;
XKeyEvent xkey;
XButtonEvent xbutton;
XMotionEvent xmotion;
XCrossingEvent xcrossing;
XFocusChangeEvent xfocus;
XExposeEvent xexpose;
/* ... */
XErrorEvent xerror;
XKeymapEvent xkeymap;
long pad[24];
};
La dclaration a juste t un peu raccourcie pour rester lisible. Les types X*Eventde
l'union sont en fait des structures contenant des champs spcifiques au
message. Par exemple, si type vaut ButtonPress, seules les valeurs du champ
xbutton seront significatives. Parmi ces champs, il y a button qui permet de
savoir quel bouton de la souris t prss:
XEvent ev;
XNextEvent(display, &ev);
switch (ev.type) {
case ButtonPress:
printf("Le bouton %d a t press.\n", ev.xbutton.button);
break;
default:
printf("Message type %d\n", ev.type);
}

68

Types avancs - structures, unions, numrations

Dfinitions de synonymes de types (typedef)


Le langage C offre un mcanisme assez pratique pour dfinir des synonymes de types. Il
s'agit du mot-cl typedef.
typedef un_type synonyme_du_type;
Contrairement aux langages typage fort comme le C++, le C se base sur les types
atomiques pour dcider de la compatibilit entre deux types. Dit plus simplement, la
dfinition de nouveaux types est plus un mcanisme d'alias qu'une relle dfinition de type.
Les deux types sont effectivement parfaitement interchangeables. la limite on pourrait
presque avoir les mmes fonctionnalits en utilisant le prprocesseur C, bien qu'avec ce
dernier vous aurez certainement beaucoup de mal sortir de tous les piges qui vous
seront tendus.

Quelques exemples
typedef
typedef
typedef
typedef

unsigned char octet;


double matrice4_4[4][4];
struct ma_structure * ma_struct;
void (*gestionnaire_t)( int );

/* Utilisation */
octet nombre = 255;
matrice4_4 identite = { {1,0,0,0}, {0,1,0,0}, {0,0,1,0}, {0,0,0,1} };
ma_struct pointeur = NULL;
gestionnaire_t fonction = NULL;
Ce mot cl est souvent utilis conjointement avec la dclaration des structures, pour ne pas
devoir crire chaque fois le mot cl struct. Elle permet aussi de grandement simplifier
les prototypes de fonctions qui prennent des pointeurs sur des fonctions en argument, ou
retournent de tels types. Il est conseill dans de tels cas de dfinir un type synonyme, plutt
que de l'crire in extenso dans la dclaration du prototype. Considrez les deux
dclarations :
/* Dclaration confuse */
void (*fonction(int, void (*)(int)))(int);
/* Dclaration claire avec typedef */
typedef void (*handler_t)(int);
handler_t fonction(int, handler_t);
Les vtrans auront reconnu le prototype imbitable de l'appel systme signal(), qui
permet de rediriger les signaux (interruption, alarme priodique, erreur de segmentation,
division par zro, ...).

69

Types avancs - structures, unions, numrations

numrations
enum nom_enum { val1, val2, ..., valN };
Les symboles val1, val2, ..., valN pourront tre utiliss littralement dans la suite du
programme. Ces symboles sont en fait remplacs par des entiers lors de la compilation. La
numrotation commenant par dfaut 0, s'incrmentant chaque dclaration. Dans
l'exemple ci-dessus, val1 vaudrait 0, val2 1 et valN N-1.
On peut changer tout moment la valeur d'un symbole, en affectant au symbole, la valeur
constante voulue (la numrotation recommenant ce nouvel indice). Par exemple :
enum Booleen { Vrai = 1, Faux = 0 };
/* Pour l'utiliser */
enum Booleen variable = Faux;
Ce qui est assez pnible en fait, puisqu'il faut chaque fois se souvenir que le type Booleen
est driv d'une numration. Il est prfrable de simplifier les dclarations, grce
l'instruction typedef :
typedef enum { Faux, Vrai } Booleen;
/* Pour l'utiliser */
Booleen variable = Faux;

Type incomplet
Pour garantir un certain degr d'encapsulation, il peut tre intressant de masquer le
contenu d'un type complexe, pour viter les usages trop optimiss de ce type. Pour cela,
le langage C permet de dclarer un type sans indiquer explicitement son contenu.
struct ma_structure;
/* Plus loin dans le code */
struct ma_structure * nouvelle = alloue_objet();
Les diffrents champs de la structure n'tant pas connus, le compilateur ne saura donc pas
combien de mmoire allouer. On ne peut donc utiliser les types incomplets qu'en tant que
pointeur. C'est pourquoi, il est pratique d'utiliser l'instruction typedef pour allger les
critures :
typedef struct ma_structure * ma_struct;
/* Plus loin dans le code */
ma_struct nouvelle = alloue_objet();
Cette construction est relativement simple comprendre, et proche d'une conception objet.
noter que le nouveau type dfini par l'instruction typedef peut trs bien avoir le mme
nom que la structure. C'est juste pour viter les ambiguits, qu'un nom diffrent a t
choisi dans l'exemple.
Un autre cas de figure relativement classique, o les types incomplets sont trs pratiques,
ce sont les structures s'auto-rfrenant, comme les listes chaines, les arbres, etc.

70

Types avancs - structures, unions, numrations


struct liste
{
struct liste * suivant;
struct liste * precedant;
void *
element;
};
Ou de manire encore plus tordue, plusieurs types ayant des rfrences croises :
struct type_a;
struct type_b;
struct type_a
{
struct type_a * champ1;
struct type_b * champ2;
int
champ3;
};
struct type_b
{
struct type_a * ref;
void *
element;
};

71

Prprocesseur

Prprocesseur
Le prprocesseur est un langage de macro qui est analys, comme son nom l'indique, avant
la compilation. En fait, c'est un langage compltement indpendant, il est mme
thoriquement possible de l'utiliser par dessus un autre langage que le C. Cette
indpendance fait que le prprocesseur ignore totalement la structure de votre programme,
les directives seront toujours values de haut en bas.
Ces directives commencent toutes par le symbole dise (#), suivi d'un nombre quelconque
de blancs (espace ou tabulation), suivi du nom de la directive en minuscule. Les directives
doivent tre dclares sur une ligne ddie. Les noms standards de directives sont :

define : dfinit un motif de substitution.


undef : retire un motif de substitution.
include : inclusion de fichier.
ifdef, ifndef, if, else, elif, endif : compilation conditionnelle.
pragma : extension du compilateur.
error : mettre un message d'erreur personnalis et stopper la compilation.

Variables de substitution et macros


Dclarations de constantes
Les variables de substitution du prprocesseur fournissent un moyen simple de nommer des
constantes. En effet :
#define CONSTANTE valeur

permet de substituer presque partout dans le code source qui suit cette ligne la suite de
caractres CONSTANTE par la valeur. Plus prcisment, la substitution se fait partout,
l'exception des caractres et des chaines de caractres. Par exemple, dans le code suivant
:
#define TAILLE 100
printf("La constante TAILLE vaut %d\n", TAILLE);
La substitution se fera sur la deuxime occurrence de TAILLE, mais pas la premire. Le
prprocesseur transformera ainsi l'appel printf :
printf("La constante TAILLE vaut %d\n", 100);
Le prprocesseur procde des traitements sur le code source, sans avoir de connaissance
sur la structure de votre programme. Dans le cas des variables de substitution, il ne sait
faire qu'un remplacement de texte, comme le ferait un traitement de texte. On peut ainsi
les utiliser pour n'importe quoi, des constantes, des expressions, voire du code plus
complexe ; le prprocesseur n'ira pas vrifier si la valeur de remplacement est une
expression C ou non. Dans l'exemple prcdent, nous avons utilis TAILLE pour remplacer
la constante 100, mais toute suite de caractres peut tre dfinie. Par exemples:

72

Prprocesseur
#define PREFIXE "erreur:"
#define FORMULE (1 + 1)
printf(PREFIXE "%d\n", FORMULE);
Ici, l'appel printf sera transform ainsi :
printf("erreur:" "%d\n", (1 + 1));
Une dfinition de constantes peut s'tendre sur plusieurs lignes. Pour cela, il faut que le
dernier caractre de la ligne soit une barre oblique inverse ('\'). On peut ainsi dfinir une
macro qui est remplace par un bout de code plus complexe :
#define BLOC_DEFAUT
\
default:
\
puts("Cas interdit."); \
break;

Cette variable de substitution permet ainsi d'ajouter le cas par dfaut un switch.
Historiquement, les programmeurs avaient pour habitude d'utiliser des capitales pour
distinguer les dclarations du prprocesseur et les minuscules pour les noms de symboles
(fonctions, variables, champs, etc.) du compilateur. Ce n'est pas une rgle suivre
imprativement, mais elle amliore la lisibilit des programmes.
Il est possible de dfinir plusieurs fois la mme CONSTANTE . Le compilateur n'mettra
un avertissement que si les deux valeurs ne concordent pas.
En fait, dans la section prcdente, il tait fait mention d'un mcanisme relativement
similaire : les numrations. On peut lgitimement se demander ce que ces numrations
apportent en plus par rapport aux directives du prprocesseur. En fait, on peut
essentiellement souligner que :
Lors des cas multiples (switch), le compilateur peut vrifier que l'ensemble des cas
couvre l'intervalle de valeur du type, et mettre un avertissement si ce n'est pas le cas.
Ce qui est videmment impossible faire avec des #define.
L o l'utilit est plus flagrante, c'est lors du dbogage. Un bon dbogueur peut afficher
le nom de l'lment numr, au lieu de simplement une valeur numrique, ce qui rends
un peu moins pnible ce processus dj trs rbarbatif la base, surtout lorsque le type
en question est une structure avec des dizaines, pour ne pas dire une centaine, de
champs.
Certains compilateurs (gcc pour ne citer que le plus connu) n'incluent pas par dfaut les
symboles du prprocesseur avec l'option standard de dbogage (-g), principalement pour
viter de faire exploser la taille de l'excutable. Si bien que dans un dbogueur il est
souvent impossible d'afficher la valeur associe une constante du prprocesseur
autrement qu'en fouillant dans les sources. moins d'avoir un environnement plutt
volu, cette limitation peut s'avrer trs pnible.

73

Prprocesseur

Dclarations automatiques
Le langage C impose que le compilateur dfinisse un certain nombre de constantes. Sans
numrer toutes celles spcifiques chaque compilateur, on peut nanmoins compter sur :
__FILE__ (char *) : une chane de caractres reprsentant le nom de fichier dans lequel
on se trouve. Pratique pour diagnostiquer les erreurs.
__LINE__ (int) : le numro de la ligne en cours dans le fichier.
__DATE__ (char *) : la date en cours (incluant le jour, le mois et l'anne).
__TIME__ (char *) : l'heure courante (HH:MM:SS).
__STDC__ (int) : cette constante est en gnral dfinie si le compilateur suit les rgles du
C ANSI (sans les spcificits du compilateur). Cela permet d'encadrer des portions de
code non portables et fournir une implmentation moins optimise, mais ayant plus de
chance de compiler sur d'autres systmes.
Extensions
Chaque compilateur peut dfinir d'autres macro, pourvu qu'elles restent dans les
conventions de nommage que la norme leur rserve. Les lister toutes ici est impossible et
hors-sujet, mais on peut citer quelques unes, titre d'exemple, que le lecteur pourra
rencontrer rgulirement dans du code. Il va de soi que le fait qu'elles soient dfinies ou
non, et leur ventuelle valeur, est entirement dpendant du compilateur.
Dtection du systme d'exploitation :
_WIN32 ou __WIN32__ (Windows)
linux ou __linux__ (Linux)
__APPLE__ ou __MACH__ (Apple Darwin)
__FreeBSD__ (FreeBSD)
__NetBSD__ (NetBSD)
sun ou __SVR4 (Solaris)
Visual C++ : _MSC_VER
Compilateur gcc :
Version : __GNUC__, __GNUC_MINOR__, __GNUC_PATCHLEVEL__.
Compilateurs Borland :
__TURBOC__ (version de Turbo C ou Borland C)
__BORLANDC__ (version de Borland C++ Builder)
Divers : __MINGW32__ (MinGW), __CYGWIN__ ou __CYGWIN32__ (Cygwin),

Dclaration de macros
Une macro est en fait une constante qui peut prendre un certain nombre d'arguments. Les
arguments sont placs entre parenthses aprs le nom de la macro sans espaces, par
exemple :
#define MAX(x,y) x > y ? x : y
#define SWAP(x,y) x ^= y, y ^= x, x ^= y
La premire macro prend deux arguments et retourne le maximum entre les deux. La
deuxime est plus subtile, elle change la valeur des deux arguments (qui doivent tre des
variables entires), sans passer par une variable temporaire, et ce avec le mme nombre
d'oprations.

74

Prprocesseur
La macro va en fait remplacer toutes les occurrences de la chane MAX et de ses
arguments par x > y ? x : y . Ainsi, si on appelle la macro de cette faon :
printf("%d\n", MAX(4,6));
Elle sera remplace par :
printf("%d\n", 4 > 6 ? 4 : 6);
Il faut bien comprendre qu'il ne s'agit que d'une substitution de texte, qui ne tient pas
compte de la structure du programme. Considrez l'exemple suivant, qui illustre une erreur
trs classique dans l'utilisation des macros :
#define MAX(x,y) x > y ? x : y
i = 2 * MAX(4,6); /* Sera remplac par : i = 2 * 4 > 6 ? 4 : 6; */
L'effet n'est pas du tout ce quoi on s'attendait. Il est donc important de bien parenthser
les expressions, justement pour viter ce genre d'effet de bord. Il aurait mieux fallu crire
la macro MAX de la manire suivante :
#define MAX(x,y) ((x) > (y) ? (x) : (y))
En fait ds qu'une macro est compose d'autre chose qu'un lment atomique (un lexme,
ou un token) il est bon de le mettre entre parenthses, notamment les arguments qui
peuvent tre des expressions utilisant des oprateurs ayant des priorits arbitraires.
Il est a noter que l'emploi systmatique de parenthses ne protge pas contre tous les
effets de bord. En effet :
MAX(x++,y); /* Sera remplac par : ((x++) > (y) ? (x++) : (y)) */
Du coup, x est incrment de 2 et non pas de 1. La qualit et la performance des
compilateurs C modernes fait que l'utilisation de fonctions inline est le plus souvent
prfrable.

Suppression d'une dfinition


Il arrive qu'une macro/constante soit dj dfinie, mais qu'on aimerait quand mme utiliser
ce nom avec une autre valeur. Pour viter un avertissement du prprocesseur, on doit
d'abord supprimer l'ancienne dfinition, puis dclarer la nouvelle :
#undef symbole
#define symbole nouvelle_valeur

Cette directive supprime le symbole spcifi. noter que mme pour les macros avec
arguments, il suffit juste de spcifier le nom de cette macro. Qui plus est, supprimer une
variable de substitution inexistante ne provoquera aucune erreur.

75

Prprocesseur

Transformation en chane de caractres


Le prprocesseur permet de transformer une expression en chane de caractres. Cette
technique ne fonctionne donc qu'avec des macros ayant au moins un argument. Pour
transformer n'importe quel argument de la macro en chane de caractres, il suffit de
prfixer le nom de l'argument par le caractre dise ('#'). Cela peut tre utile pour afficher
des messages de diagnostique trs prcis, comme dans l'exemple suivant :
#define assert(condition) \
if( (condition) == 0 ) \
{ \
puts( "La condition '" #condition "' a chou" ); \
exit( 1 );
\
}

noter qu'il n'existe pas de mcanisme simple pour transformer la valeur de la macro en
chane de caractres. C'est le cas classique des constantes numriques qu'on aimerait
souvent transformer en chane de caractres : le prprocesseur C n'offre malheureusement
rien de vraiment pratique pour effectuer ce genre d'opration.

Concatnation d'arguments
Il s'agit d'une facilit d'criture pour simplifier les tches rptitives. En utilisant
l'oprateur ##, on peut concatner deux expressions :
#define version(symbole) symbole ## _v123
int version(variable); /* Dclare "int variable_v123;" */

Dclaration de macros nombre variable d'arguments


Ceci est une nouveaut du C99. La dclaration d'une macro nombre variable d'arguments
est en fait identique une fonction, sauf qu'avec une macro on ne pourra pas traiter les
arguments supplmentaires un un. Ces paramtres sont en fait traits comme un tout, via
le symbole __VA_ARGS__. Exemple :
#define debug(message, ...) fprintf( stderr, __FILE__ ":%d:" message
"\n", __LINE__, __VA_ARGS__ )
Il y a une restriction qui ne saute pas vraiment aux yeux dans cet exemple, c'est que les
points de suspension doivent obligatoirement tre remplacs par au moins un
argument, ce qui n'est pas toujours trs pratique. Malheureusement le langage C n'offre
aucun moyen pour contourner ce problme pourtant assez rpandu.
noter, une extension du compilateur gcc, qui permet de s'affranchir de cette limitation
en rajoutant l'oprateur ## __VA_ARGS__ :
/* Extension de gcc pour utiliser la macro sans argument */
#define debug(message, ...) fprintf( stderr, __FILE__ ":%d:" message
"\n", __LINE__, ##__VA_ARGS__ )
Ces macros peuvent tre utilises par exemple pour des traitements autour de la fonction
printf, voir par exemple Variadic Macros (en).

76

Prprocesseur

Exemples
Les macro du prprocesseur sont souvent utilises dans des situations o une fonction,
ventuellement inline, aurait le mme effet, avec une meilleure robustesse. Cependant, il
y a des usages pour lesquels les macros ne peuvent tre remplacs par des fonctions.
Par exemple, pour afficher la taille des types C, on crirait un programme comme le suivant
:
#include <stdio.h>
int main(void)
{
printf("sizeof(char) = %zd.\n", sizeof(char));
printf("sizeof(short) = %zd.\n", sizeof(short));
printf("sizeof(int) = %zd.\n", sizeof(int));
/* ...*/
return 0;
}
crire un tel programme peut tre fatiguant, et il est trs facile de faire des erreurs en
recopiant les lignes. Ici, une fonction ne pourrait simplifier l'criture. En effet, il faudrait
passer la fonction le nom du type, pour l'afficher dans la chane "sizeof(XXX) = ", et la
taille. On devrait donc donner deux arguments la fonction, pour l'appeler ainsi:
print_taille("char", sizeof(char));
Ce qui ne serait pas beaucoup plus simple, surtout que la fonction elle-mme serait plus
complexe (utilisation de fonctions str* pour inclure le premier paramtre dans la chane
de format). Ici, le prprocesseur fournit une solution beaucoup plus simple, l'aide de
l'oprateur # :
#include <stdio.h>
#define PRINTF_SIZEOF(type) printf("sizeof(" #type ") = %zd.\n",
sizeof(type))
int main(void)
{
PRINTF_SIZEOF(char);
PRINTF_SIZEOF(short);
PRINTF_SIZEOF(int);
return 0;
}
Une technique similaire peut tre utilise pour conserver l'affichage le nom de constantes
ou d'numrations :
#include <stdio.h>
enum etat_t { Arret, Demarrage, Marche, ArretEnCours };
#define CASE_ETAT(etat) case etat: printf("Dans l'tat " #etat "\n");

77

Prprocesseur

78

break;
void affiche_etat(etat_t etat)
{
switch (etat)
{
CASE_ETAT(Arret)
CASE_ETAT(Demarrage)
CASE_ETAT(Marche)
CASE_ETAT(ArretEnCours)
default:
printf("Etat inconnu (%d).\n, etat);
break;
}
}
Ces exemples sont tirs d'un message

[2]

de Bill Godfrey sur comp.lang.c.

Compilation conditionnelle
Les tests permettent d'effectuer de la compilation conditionnelle. La directive #ifdef
permet de savoir si une constante ou une macro est dfinie. Chaque dclaration #ifdef
doit obligatoirement se terminer par un #endif, avec ventuellement une clause #else
entre. Un petit exemple classique :
#ifdef DEBUG
/* S'utilise : debug( ("Variable x = %d\n", x) ); (double parenthsage)
*/
#define debug(x) printf x
#else
#define debug(x)
#endif
L'argument de la directive #ifdef doit obligatoirement tre un symbole du prprocesseur.
Pour utiliser des expressions un peu plus complexes, il y a la directive #if (et #elif,
contraction de else if). Cette directive utilise des expressions semblable l'instruction
if() : si l'expression value est diffrente de zro, elle sera considr comme vraie, et
comme fausse si l'expression s'value 0.
On peut utiliser l'addition, la soustraction, la multiplication, la division, les oprateurs
binaires (&, |, ^, ~, <<, >>), les comparaisons et les connecteurs logiques (&& et ||). Ces
derniers sont valus en circuit court comme leur quivalent C. Les oprandes possibles
sont les nombres entiers et les caractres, ainsi que les macros elle-mmes, qui seront
remplacs par leur valeur.
noter, l'oprateur spcial defined qui permet de tester si une macro est dfinie (et qui
renvoit donc un boolen). Exemple :
#if defined(DEBUG) && defined(NDEBUG)
#error DEBUG et NDEBUG sont dfinis !
#endif

Prprocesseur
Cette directive est trs pratique pour dsactiver des pans entier de code sans rien y
modifier. Il suffit de mettre une expression qui vaudra toujours zro, comme :
#if 0
/* Vous pouvez mettre ce que vous voulez ici, tout sera ignor, mme du
code invalide */
:-)
#endif
Un autre avantage de cette technique, est que les directives de compilations peuvent tre
imbriques, contrairement aux commentaires. Dans ce cas, s'il y avait eu d'autres directives
#if / #endif (correctement balances), la clause #if 0 ne s'arrterait pas au premier
#endif rencontr, tandis que cela aurait t le cas avec des commentaires.
noter qu'au niveau du prprocesseur il est impossible d'utiliser les oprateurs du C.
Notamment l'oprateur sizeof, dont le manque aura fait grincer les dents des
gnrations de programmeurs, sera en fait interprt comme tant une macro. Il faut bien
garder l'esprit que le prprocesseur C est totalement indpendant du langage C.

Inclusion de fichiers
Il s'agit d'une autre fonctionnalit massivement employe dans toute application C qui se
respecte. Comme son nom l'indique, la directive #include permet d'inclure in extenso le
contenu d'un autre fichier, comme s'il avait t crit en lieu et place de la directive. On
peut donc voir a comme de la factorisation de code, ou plutt de dclarations. On appelle
de tels fichiers, des fichiers en-ttes (header files) et on leur donne en gnral l'extension
.h.
Il est rare d'inclure du code dans ces fichiers (dfinitions de variables ou de fonctions),
principalement parce que ces fichiers sont destins tre inclus dans plusieurs endroits du
programme. Ces dfinitions seraient donc dfinies plusieurs fois, ce qui, dans le meilleur
des cas, seraient du code superflu, et dans le pire, pourraient poser des problmes
l'dition des liens.
On y place surtout des dclarations de type, macros, prototypes de fonctions relatif un
module. On en profite aussi pour documenter toutes les fonctions publiques, leurs
paramtres, les valeurs renvoyes, les effets de bords, la signification des champs de
structures et les pr/post conditions (quel tat doit respecter la fonction avant/aprs son
appel). Dans l'idal on devrait pouvoir comprendre le fonctionnement d'un module,
simplement en lisant son fichier en-tte.
La directive #include prend en fait un argument : le nom du fichier que vous voulez
inclure. Ce nom doit tre soit mis entre guillemets doubles ou entre balises (< >). Cette
diffrence affecte simplement l'ordre de recherche du fichier. Dans le premier cas, le
fichier est recherch dans le rpertoire o le fichier contenant la directive se trouve, puis le
prprocesseur regarde des endroits prconfigurs. Dans le second cas, il regardera
seulement dans les endroits prconfigurs. Les endroits prconfigurs sont le rpertoire
include par dfaut (par exemple /usr/include/, sous Unix) et ceux passs explicitement en
paramtre au compilateur.
En fait l'argument de la directive #include peut aussi tre une macro, dont la valeur est un
argument valide (soit une chane de caractres, soit un nom entre balises < et >) :

79

Prprocesseur
#define FICHIER_A_INCLURE <stdio.h>
#include FICHIER_A_INCLURE

Exemple
fichier.h
void affiche( void );
fichier.c
#include "fichier.h"
int main( void )
{
affiche();
return 0;
}
C'est comme si fichier.c avait t crit :
void affiche( void );
int main( void )
{
affiche();
return;
}

Protection contre les inclusions multiples


noter un problme relativement rcurrent avec les fichiers en-ttes : il s'agit des
inclusions multiples. mesure qu'une application grandit, il arrive frquemment qu'un
fichier soit inclus plusieurs fois la suite d'une directive #include. Quand bien mme les
dclarations sont identiques, dfinir deux types avec le mme nom n'est pas permis en C.
On peut nanmoins s'en sortir avec cette technique issue de la nuit des temps :
#ifndef H_MON_FICHIER
#define H_MON_FICHIER
/* Mettre toutes les dclarations ici */
#endif
C'est un problme tellement classique, que le premier rflexe lors de l'criture d'un tel
fichier est de rajouter ces directives. H_MON_FICHIER est bien-sr adapter chaque
fichier, l'essentiel est que le nom soit unique dans toute l'application. Habituellement on
utilise le nom du fichier, mis en majuscule, avec les caractres non-alphabtiques
remplacs par des souligns.

80

Prprocesseur

Avertissement et message d'erreur personnaliss


Il peut tre parfois utile d'avertir l'utilisateur que certaines combinaisons d'options sont
dangereuses ou carrment invalides. Le prprocesseur dispose d'une directive pour
effectuer cela:
#error message

Cette directive affichera en fait le message tel quel, qui peut videmment contenir
n'importe quoi, y compris des caractres rservs, sans qu'on ait besoin de le mettre entre
guillement ("). Aprs mission du message, la compilation s'arrtera.
noter que certains compilateurs comme GCC proposent aussi la directive:
#warning message

qui permet d'afficher un message sans arrter la compilation.

Rfrences
[1] http:/ / www. redhat. com/ docs/ manuals/ enterprise/ RHEL-3-Manual/ cpp/ variadic-macros. html
[2] http:/ / groups. google. fr/ group/ comp. lang. c/ msg/ 19f07545672ba9e7

Bibliothque standard
La bibliothque standard du langage C peut paratre relativement pauvre par rapport
d'autres langages tout en un plus rcents comme Python, Ruby, Perl, C# ou Java. Conue
avant tout avec un souci de portabilit, et en ayant en tte les contraintes matrielles
limites de certaines plate-formes auxquelles le C est aussi destin, vous obtenez avec cela
le plus petit dnominateur commun qui puisse tre port sur le plus grand nombre de
plateformes. Les contraintes qu'ont subies l'ANSI, et le WG14 aprs lui, portant
principalement sur les contraintes de portabilit sur certaines architectures parfois
exotiques , et la trs forte contrainte de ne pas casser du code existant reposant sur un
comportement dj tabli, voire normalis, font que certains points connus pour tre
complexes, voire peu souhaitables, sont rests dans le langage et la bibliothque standard.
Concevoir une application avec les seules fonctions prsentes dans cette bibliothque
ncessite une trs grande rigueur. Le WG14 ne s'tant pas fix pour but d'tendre la
bibliothque standard de manire importante, il est plus que conseill de se tourner vers
des bibliothques de plus haut niveau, afin d'viter de rinventer inutilement la roue. Il en
existe heureusement beaucoup, mais dcrire ne serait-ce que ce qui existe est hors de la
porte de cet ouvrage.
La bibliothque standard permet toutefois de faire des traitements complexes avec peu
d'efforts, pour peu qu'on ait conscience des dangers et des piges qui sont parfois tendus.
Les sections qui suivent permettront de voir un peu plus clair dans les mandres parfois
trs sombres o s'aventure le C.

81

Bibliothque standard

Voir aussi
Cours sur Wikiversity sur l'utilisation des fonctions standards du langage C.

Chanes de caractres
Le langage C offre quelques facilits d'critures pour simuler les chanes de caractres
l'aide de tableaux. En plus de cela, certaines fonctions de la bibliothque standard (et les
autres) permettent de faciliter leur gestion. la diffrence d'un tableau, les chanes de
caractres respectent une convention : se terminer par le caractre nul, '\0'
(antislash-zero). Ainsi, pour construire une chane de caractres la main , il ne faut pas
oublier ce caractre final.
On peut noter que, par rapport ce qui est disponible dans d'autres langages, les fonctions
de la bibliothque standard sont peu pratiques utiliser. On peut avancer sans trop de
risque qu'une des plus grandes lacunes du C provient de sa gestion prcaire des chanes de
caractres, pourtant massivement employes dans tout programme digne de ce nom. Pour
faire court, on ne saurait trop conseiller de soit reprogrammer les fonctions ci-dessous,
d'utiliser une bibliothque externe ou de faire preuve de paranoa avant leur utilisation.
Les fonctions permettant de manipuler les chanes de caractres se trouvent dans l'entte
string.h, ainsi pour les utiliser il faut ajouter la commande prprocesseur :
#include <string.h>

Comparaison de chanes
int strcmp(const char * chaine1, const char * chaine2);
int strncmp(const char * chaine1, const char * chaine2, size_t
longueur);
Compare les chanes chaine1 et chaine2 et renvoie un entier :
ngatif, si chaine1 est infrieure chaine2 ;
nul, si chaine1 est gale chaine2 ;
positif, si chaine1 est suprieur chaine2.
premire remarque : lorsque les deux chanes sont gales, strcmp renvoie 0, qui a la
valeur de vrit faux. Pour tester l'galit entre deux chanes, il faut donc crire soit if
(strcmp(chaine1, chaine2) == 0) ..., soit if (!strcmp(chaine1, chaine2)) ...
mais surtout pas if (strcmp(chaine1, chaine2)) ... qui teste si deux chanes sont
diffrentes !
seconde remarque : l'oprateur ==, dans le cas de pointeurs, teste si les adresses sont
gales. Noter chaine1 == chaine2, si chaine1 et chaine2 sont des char * revient tester
si les deux chanes pointent sur la mme zone mmoire et non pas tester l'galit de leur
contenu.
noter l'existence de deux fonctions de comparaisons de chane, insensibles la casse des
caractres et s'adaptant la localisation en cours, fonctionnant sur le mme principe que
strcmp() et strncmp(), mais dont l'origine provient des systmes BSD :
int strcasecmp(const char * chaine1, const char * chaine2);
int strncasecmp(const char * chaine1, const char * chaine2, size_t

82

Chanes de caractres
longueur);

Longueur d'une chane


size_t strlen(const char * chaine);
Renvoie la longueur de la chaine sans compter le caractre '\0'.
exemple : strlen("coincoin") renvoie 8.

Concatnation et copie de chanes


char
char
char
char

*
*
*
*

strcpy (char *destination, const char *source);


strncpy (char *destination, const char *source, size_t n);
strcat (char *destination, const char *source);
strncat (char *destination, const char *source, size_t n);

strcpy copie le contenu de source l'adresse mmoire point par destination, incluant le
caractre nul final. strcat concatne la chane source la fin de la chane destination, en
y rajoutant aussi le caractre nul final. Toutes ces fonctions renvoient le pointeur sur la
chane destination.
Pour strncpy, on notera que:
si la chane source a moins de n caractres non nuls, strncpy rajoutera n strlen(source) caractres nuls la suite, pour complter;
si la chane source fait au moins n caractres, la fonction n'insrera pas de caractre
nul en fin de chaine (i.e. destination ne sera pas une chane de caractres valide).
Il faut tre trs prudent lors des copies ou des concatnations de chanes, car les problmes
pouvant survenir peuvent tre trs pnibles diagnostiquer. L'erreur classique est de
faire une copie dans une zone mmoire non rserve ou trop petite, comme dans l'exemple
suivant, a priori anodin:
Ce code contient une erreur volontaire !
char *copie_chaine(const char *source)
{
char *chaine = malloc(strlen(source));
if (chaine != NULL)
{
strcpy(chaine, source);
}
return chaine;
}
Ce code a priori correct provoque pourtant l'criture d'un caractre dans une zone non
alloue. Les consquences de ce genre d'action sont totalement imprvisibles, pouvant au
mieux passer inaperue, ou au pire craser des structures de donnes critiques dans la
gestion des blocs mmoires et engendrer des accs mmoire illgaux lors des prochaines
tentatives d'allocation ou de libration de bloc, i.e. le cauchemar de tout programmeur.
Dans cet exemple il aurait bien videmment fallu crire :
char * chaine = malloc( strlen(source) + 1 );

83

Chanes de caractres
Une autre erreur, beaucoup plus frquente hlas, est de copier une chane dans un tableau
de caractres local, sans se soucier de savoir si ce tableau est capable d'accueillir la
nouvelle chane. Les consquences peuvent ici tre beaucoup plus graves qu'un simple
accs illgal une zone mmoire globale.
Un crasement mmoire (buffer overflow) est considr comme un dfaut de scurit
relativement grave, puisque, sur un grand nombre d'architectures, un attaquant bien
renseign sur la structure du programme peut effectivement lui faire excuter du code
arbitraire. Ce problme vient de la manire dont les variables locales aux fonctions et
certaines donnes internes sont stockes en mmoire. Comme le C n'interdit pas d'accder
un tableau en dehors de ses limites, on pourrait donc, suivant la qualit de
l'implmentation, accder aux valeurs stockes au-del des dclarations de variables
locales. En fait, sur un grand nombre d'architectures, les variables locales sont places
dans un espace mmoire appel pile, avec d'autres informations internes au systme,
comme l'adresse de retour de la fonction, c'est--dire l'adresse de la prochaine instruction
excuter aprs la fin de la fonction. En s'y prenant bien, on peut donc craser cette valeur
pour la remplacer par l'adresse d'un autre bout de code, qui donnerait l'ordre d'effacer le
disque dur, par exemple ! Si en plus le programme possde des privilges, les rsultats
peuvent tre assez catastrophiques. Ainsi dcrit, le problme semble complexe : il faudrait
que l'attaquant puisse insrer dans une zone mmoire de l'application un bout de code qu'il
a lui-mme crit, et qu'il arrive crire l'adresse de ce bout de code l o le systme
s'attend retrouver l'adresse de retour de la fonction. Cependant, ce genre d'attaque est
aujourd'hui trs courant, et les applications prsentant ce genre d'erreur deviennent trs
rapidement la cible d'attaques.
Voici un exemple trs classique, o ce genre d' exploit peut arriver :
Ce code contient une erreur volontaire !
int traite_chaine(const char *ma_chaine)
{
char tmp[512];
strcpy(tmp, ma_chaine);
/* ... */
}
Ce code, hlas plus frquent qu'on ne le pense, est bannir. Parmi diffrentes mthodes,
on peut viter ce problme en ne copiant qu'un certain nombre des premiers caractres de
la chane, avec la fonction strncpy :
char tmp[512];
strncpy(tmp, ma_chaine, sizeof(tmp));
tmp[sizeof(tmp) - 1] = '\0';
On notera l'ajout explicite du caractre nul, si ma_chaine est plus grande que la zone
mmoire tmp. La fonction strncpy ne rajoutant hlas pas, dans ce cas, de caractre nul.
C'est un problme tellement classique que toute application C reprogramme en gnral la
fonction strncpy pour prendre en compte ce cas de figure (voir la fonction POSIX strdup,
ou strlcopy utilise par le systme d'exploitation OpenBSD, par exemple)

84

Chanes de caractres
Le langage n'offrant que trs peu d'aide, la gestion correcte des chanes de caractres est
un problme ne pas sous-estimer en C.

Recherche dans une chane


char * strchr(const char * chaine, int caractre);
char * strrchr(const char * chaine, int caractre);
Recherche le caractre dans la chaine et renvoie la position de la premire occurence dans
le cas de strchr et la position de la dernire occurence dans le cas de strrchr.
char * strstr(const char * meule_de_foin, const char * aiguille);
Recherche l'aiguille dans la meule de foin et renvoie la position de la premire occurence.

Traitement des blocs mmoire


La bibliothque string.h contient encore quelques fonctions pour la manipulation de zone
brute de mmoire. Ces fonctions, prfixes par mem, sont les quivalents des fonctions str*
pour des zones de mmoire qui ne sont pas des chanes de caractres. Cela peut tre des
tableaux non termins par la valeur 0, comme des tableaux pouvant contenir la valeur 0
avant la fin, par exemple. Elles permettent aussi de traiter des structures (par exemple
pour copier les donnes d'une structure dans une autre), ou des zones de mmoires
alloues dynamiquement (par exemple pour initialiser une zone mmoire alloue par
malloc).
Comme, au contraire des fonctions str*, elles n'ont pas de dlimiteur de fin de tableau, il
faut leur donner en paramtre la taille de ces tableaux (cette taille tant en bytes au sens
strict, bien que souvent en octets).
void memcpy( void * destination, const void * source, size_t longueur );
Copie 'longueur' octet(s) de la zone mmoire 'source' dans 'destination'. Vous devez bien
sr vous assurer que la chaine destination ait suffisament de place, ce qui est en gnral
plus simple dans la mesure o l'on connait la longueur.
Attention au rcouvrement des zones : si destination + longueur < source alors
source >= destination.
void memmove( void * destination, const void * source, size_t longueur )
Identique la fonction memcpy(), mais permet de s'affranchir totalement de la limitation de
recouvrement.
void memset( void * memoire, int caractere, size_t longueur );
Initialise les 'longueur' premiers octets du bloc 'memoire', par la valeur convertie en type
char de 'caractere'. Cette fonction est souvent employe pour mettre zro tous les
champs d'une structure ou d'un tableau :
struct MonType_t mem;
memset( &mem, 0, sizeof mem );
int memcmp( const void * mem1, const void * mem2, size_t longueur );

85

Chanes de caractres
Compare les 'longueur' premiers octets des blocs 'mem1' et 'mem2'. Renvoie les codes
suivants :
< 0 : mem1 < mem2
= 0 : mem1 == mem2
> 0 : mem1 > mem2
void * memchr( const void * memoire, int caractere, size_t longueur );
Recherche dans les 'longueur' premiers octets du bloc 'memoire', la valeur convertie en
type char de 'caractere'. Renvoie un pointeur sur l'emplacement o le caractre a t
trouv, ou NULL si rien n'a t trouv.
void * memrchr( const void * memoire, int caractere, size_t longueur );
Pareil que memchr(), mais commence par la fin du bloc 'memoire'.

Entres/sorties
Les fonctions d'entres/sorties sont celles qui vous permettent de communiquer avec
l'extrieur, c'est--dire, la console, les fichiers, tubes de communication, socket IP, etc ...
Pour utiliser ces fonctions, il faut inclure l'en-tte stdio.h, avec la directive d'inclusion :
#include <stdio.h>

Manipulation de fichiers
En C, les fichiers ouverts sont reprsents par le type FILE, qui est un type opaque : on ne
connat pas la nature relle du type, mais seulement des fonctions pour le manipuler. Ainsi,
on ne peut crer directement de variables de type FILE, seules les fonctions de la
bibliothque standard peuvent crer une variable de ce type, lors de l'ouverture d'un
fichier. Ces donnes sont donc uniquement manipules par des pointeurs de type FILE *.
Ce type est un flux de donnes, qui reprsente des fichiers, mais peut aussi reprsenter
toute autre source ou destination de donnes. L'en-tte stdio.h fournit trois flux que l'on
peut utiliser directement :
stdin, l'entre standard ;
stdout, la sortie standard ;
stderr, la sortie standard des erreurs.
Souvent, l'entre standard envoie au programme les donnes issues du clavier, et les
sorties standard envoient les donnes que le programme gnre l'cran. Mais d'o
viennent et o vont ces donnes dpend troitement du contexte et de l'implmentation ; la
bibliothque standard fournit le type FILE comme une abstraction pour les manipuler tous
de la mme manire, ce qui libre le programmeur de certains dtails d'implmentations, et
permet l'utilisateur d'un programme d'employer (suivant son implmentation) aussi bien
son clavier qu'un fichier comme entre standard.

86

Entres/ sorties

87

Ouverture
FILE * fopen(const char * restrict chemin, const char * restrict mode)
Ouvre le fichier dsign par le chemin et renvoie un nouveau flux de donnes pointant sur
ce fichier. L'argument mode est une chane de caractres dsignant la manire dont on
veut ouvrir le fichier :
r : ouvre le fichier en lecture, le flux est positionn au dbut du fichier ;
r+ : ouvre le fichier en lecture/criture, le flux est positionn au dbut du fichier ;
w : ouvre le fichier en criture, supprime toutes les donnes si le fichier existe et le cre
sinon, le flux est positionn au dbut du fichier ;
w+ : ouvre le fichier en lecture/criture, supprime toutes les donnes si le fichier existe
et le cre sinon, le flux est positionn au dbut du fichier ;
a : ouvre le fichier en criture, cre le fichier s'il n'existe pas, le flux est positionn la
fin du fichier ;
a+ : ouvre le fichier en lecture/criture, cre le fichier s'il n'existe pas, le flux est
positionn la fin du fichier.

Rsum des modes


mode

lecture

r+

w
w+

a
a+

criture

cre le fichier

vide le fichier

position du flux
dbut

dbut

dbut

dbut

fin

fin

Lorsqu'un fichier est ouvert en criture, les donnes qui sont envoyes dans le flux ne sont
pas directement crites sur le disque. Elles sont stockes dans un tampon, une zone
mmoire de taille finie. Lorsque le tampon est plein, les donnes sont purges (flush), elles
sont crites dans le fichier. Ce mcanisme permet de limiter les accs au systme de
fichiers et donc d'acclerer les oprations sur les fichiers.
noter une particularit des systmes Microsoft Windows, est de traiter diffremment les
fichiers textes, des fichiers binaires. Sur ces systmes, le caractre de saut de ligne est en
fait compos de deux caractres (CR, puis LF, de code ASCII respectif 13 et 10, ou '\r' et
'\n' crit sous forme de caractre C). Lorsqu'un fichier est ouvert en mode texte (mode par
dfaut), toute squence CRLF lue depuis le fichier sera convertie en LF, et tout caractre
LF crit sera en fait prcd d'un caractre CR supplmentaire. Si le fichier est ouvert en
mode binaire, aucune conversion n'aura lieu.
Ce genre de comportement issu d'un autre ge, est en fait bien plus agaant que rellement
utile. Le premier rflexe est en gnral de dsactiver ce parasitage des entres/sorties, tant
il est pnible. Pour cela deux cas de figure :
1. Vous avez accs au nom du fichier : donc vous pouvez utiliser la fonction
fopen(). Dans ce cas de figure, il suffit de rajouter la lettre
b au mode d'ouverture:

Entres/ sorties
fopen("fichier.txt", "rb"); /* Ouverture sans conversion */
fopen("fichier.txt", "wb"); /* L'criture d'un '\n' n'entrainera pas
l'ajout d'un '\r' */
2. Vous n'avez pas accs au nom du fichier (par exemple stdout ou
stdin). Il existe une fonction spcifique Microsoft Windows, non
portable sur d'autres systmes, mais qui peut s'avrer ncessaire:
/* Ce code qui suit n'est pas portable */
#include <fcntl.h>
setmode(descripteur, O_BINARY);
Histoire de compliquer encore un petit peu, cette fonction travaille avec les descripteurs de
fichier, plutt que les flux stdio. On peut tout de mme s'en sortir avec cette fonction
faisant partie de la bibliothque standard:
int fileno(FILE *);
Par exemple pour mettre la sortie standard en mode binaire:
setmode(fileno(stdout), O_BINARY);

Fermeture
int fclose(FILE * flux);
Dissocie le flux du fichier auquel il avait t associ par fopen. Si le fichier tait ouvert en
criture, le tampon est vid. Cette fonction renvoie 0 si la fermeture s'est bien passe
(notamment la purge des zones en criture), ou EOF en cas d'erreur (voir le paragraphe sur
la gestion d'erreurs).

Suppression
int remove(const char * path);
Supprime le fichier ou le rpertoire nomm 'path'. La fonction renvoie 0 en cas de russite
et une valeur non nulle en cas d'erreur, ce qui peut inclure :

un rpertoire n'est pas vide ;


vous n'avez pas les permissions pour effacer le fichier (mdia en lecture seule) ;
le fichier est ouvert ;
etc.

88

Entres/ sorties

Renommage (ou dplacement)


int rename(const char * ancien_nom, const char * nouveau_nom);
Cette fonction permet de renommer l'ancien fichier ou rpertoire nomm 'ancien_nom' par
'nouveau_nom'. Elle peut aussi servir a dplacer un fichier, en mettant le chemin absolu ou
relatif du nouvel emplacement dans 'nouveau_nom'.
La fonction renvoie 0 si elle russie et une valeur non nulle en cas d'erreur.
Les causes d'erreur dpendent de l'implmentation, et peuvent tre:

vous tentez d'craser un rpertoire par un fichier ;


vous voulez craser un rpertoire non vide ;
vous n'avez pas les permissions suffisantes ;
les deux noms ne sont pas sur la mme partition ;
etc.

Dplacement dans le flux


int fseek( FILE * flux, long deplacement, int methode );
long ftell( FILE * flux );
fseek permet de se dplacer une position arbitraire dans un flux. Cette fonction renvoie 0
en cas de russite.
deplacement indique le nombre d'octet avancer (ou reculer si ce nombre est ngatif)
partir du point de rfrence (methode) :
SEEK_SET : le point de rfrence sera le dbut du fichier.
SEEK_CUR : le point de rfrence sera la position courante dans le fichier.
SEEK_END : le point de rfrence sera la fin du fichier.
ftell permet de savoir quelle position se trouve le curseur (ce depuis le dbut).
En cas d'erreur, ces deux fonctions renvoient -1.
Plusieurs remarques peuvent tre faites sur ces deux fonctions :
1. Sur une machine 32bits pouvant grer des fichiers d'une taille de 64bits (plus de 4Go),
ces fonctions sont limite inutilisables, du fait qu'un type long sur une telle architecture
est limit 32bits. On mentionnera les fonctions fseeko() et ftello() qui utilisent le
type opaque off_t, la place du type int, l'image des appels systmes. Ce type off_t
est cod sur 64bits sur les architectures le supportant et 32bits sinon. La disponibilit de
ces fonctions est en gnral limits aux systmes Unix, puisque dpendantes de la
spcification Single Unix (SUS).
2. Il faut bien sr que le priphrique o se trouve le fichier supporte une telle opration.
Dans la terminologie Unix, on appelle cela un priphrique en mode bloc. la diffrence
des priphriques en mode caractre (stdin, stdout, tube de communication, connexion
rseau, etc ...) pour lesquels ces appels choueront.

89

Entres/ sorties

Synchronisation
int fflush ( FILE *flux );
Cette fonction purge toutes les zones mmoires en attente d'criture et renvoie 0 si tout
c'est bien pass, ou EOF en cas d'erreur. Si NULL est pass comme argument, tous les flux
ouverts en criture seront purgs.
noter que cette fonction ne permet pas de purger les flux ouverts en lecture (Pour
rpondre une question du genre Voulez-vous effacer ce fichier (o/n) ? ). Une
instruction de ce genre sera au mieux ignore, et au pire provoquera un comportement
indtermin :
Ce code contient une erreur volontaire !
fflush( stdin );

Pour effectuer une purge des flux ouverts en lecture, il faut passer par des appels systmes
normaliss dans d'autres documents (POSIX), mais dont la disponibilit est en gnral
dpendante du systme d'exploitation.

Sorties formates
int printf(const char * restrict format, ...);
int fprintf(FILE * restrict flux, const char * restrict format, ...);
int sprintf(char * restrict chaine, const char * restrict format, ...);
int snprintf(char * restrict chaine, size_t taille, const char *
restrict format, ...);
Ces fonctions permettent d'crire des donnes formates dans :
la sortie standard pour printf ;
un flux pour fprintf ;
une chane de caractres pour sprintf.
En retour elle indique le nombre de caractres qui a t crit l'cran, dans le flux ou la
zone mmoire (caractre nul non compris pour sprintf).
Bien que cela dj t trait dans la section ddie aux chaines de caractres, il faut faire
trs attention avec la fonction sprintf(). Dans la mesure o la fonction n'a aucune ide de
la taille de la zone mmoire transmise, il faut s'assurer qu'il n'y aura pas de dbordements.
Mieux vaut donc utiliser la fonction snprintf(), qui permet de limiter explicitement le
nombre de caractre crire.
noter que snprintf() devrait toujours retourner la taille de la chaine crire,
indpendamment de la limite fixe par le paramtre taille. Le conditionnel reste de mise,
car beaucoup d'implmentations de cette fonction se limitent retourner le nombre de
caractres crit, c'est dire en s'arrtant la limite le cas chant.

90

Entres/ sorties

91

Type de conversion
Mis part l' endroit o crivent les fonctions, elles fonctionnent exactement de la mme
manire, nous allons donc dcrire leur fonctionnement en prenant l'exemple de printf.
L'argument format est une chane de caractres qui dtermine ce qui sera affich par
printf et sous quelle forme. Cette chane est compose de texte normal et de
squences de contrle permettant d'inclure des variables dans la sortie. Les squences de
contrle commencent par le caractre % suivi d'un caractre parmi :
d ou i pour afficher un entier sign au format dcimal (int) ;
u pour un entier non sign au format dcimal ;
x ou X pour afficher un entier au format hexadcimal (avec les lettres "abcdef" pour le
format 'x' et "ABCDEF" avec le format 'X') ;
f pour afficher un rel (double) avec une prcision fixe ;
e pour afficher un rel (double) en notation scientifique ;
g effectue un mixe de 'f' et de 'e' suivant le format le plus appropri ;
c pour afficher en tant que caractre ;
s pour afficher une chaine de caractre C standard ;
p pour afficher la valeur d'un pointeur, gnralement sous forme hexadcimale. Suivant
le compilateur, c'est l'quivalent soit "%08x", ou alors "0x%08x". ;
n ce n'est pas un format d'affichage et l'argument associ doit tre de type int * et tre
une rfrence valide. La fonction stockera dans l'entier point par l'argument le nombre
de caractres crit jusqu' maintenant ;
% pour afficher le caractre '%'.

Contraindre la largeur des champs


Une autre fonctionnalit intressante du spcificateur de format est que l'on peut spcifier
sur combien de caractre les champs seront aligns. Cette option se place entre le '%' et le
format de conversion et se compose d'un signe '-' optionnel suivit d'un nombre,
ventuellement d'un point et d'un autre nombre ([-]<nombre>[.<nombre>]). Par exemple:
%-30.30s.
Le premier nombre indique sur combien de caractre se fera l'alignement. Si la valeur
convertie est plus petite, elle sera aligne sur la droite, ou la gauche si un signe moins est
prsent au dbut. Si la valeur est plus grande que la largeur spcifie, le contenu s'tendra
au-del, dcalant tout l'alignement. Pour viter a, on peut spcifier un deuxime nombre
au del duquel le contenu sera tronqu. Quelques exemples:
printf("%10s",
printf("%-10s",
printf("%10s",
printf("%10.10s",

"Salut");
"Salut");
"Salut tout le monde");
"Salut tout le monde");

/*
/*
/*
/*

" Salut" */
"Salut " */
"Salut tout le monde" */
"Salut tout" */

Entres/ sorties

92

Contraindre la largeur des champs numriques


On peut aussi paramtrer la largeur du champ, en spcifiant * la place. Dans ce cas, en
plus de la variable afficher, il faut donner avant un entier de type int pour dire sur
combien de caractres l'alignement se fera :
printf("%-*d", 10, 1234); /* "1234 " */
printf("%*d", 10, 1234); /* " 1234" */
noter que pour le formattage de nombres entiers, la limite dure du spcificateur de
format est sans effet, pour viter de facheuses erreurs d'interprtation. On peut toutefois
utiliser les extensions suivantes :
0 : Si un zro est prsent dans le spcificateur de largeur, le nombre sera align avec
zros au lieu de blancs.
+ : Si un signe plus est prsent avec le spcificateur de largeur, le signe du nombre sera
affich tout le temps (0 est considr comme positif).
(espace) : Si le nombre est positif, un blanc sera mis avant, pour l'aligner
avec les nombres ngatifs. Exemples :
printf("%+010d",
printf("%-+10d",
printf("%-+10d",
printf("%-+10.10d",
printf("%08x",

543);
543);
1234567890);
1234567890);
543);

/*
/*
/*
/*
/*

"+000000543"
"+543 "
"+1234567890"
"+1234567890"
"0000021f"

*/
*/
*/
*/
*/

Contraindre la largeur des champs rels


Pour les rels, la limite dure sert en fait indiquer la prcision voulue aprs la virgule :
printf("%f",
3.1415926535); /* "3.141593"
*/
printf("%.8f", 3.1415926535); /* "3.14159265" */

Spcifier la taille de l'objet


Par dfaut, les entiers sont prsuposs tre de type int, les rels de type double et les
chaines de caractres de type char *. Il arrive toutefois que les types soient plus grands (et
non plus petits cause de la promotion des types, c.f. paragraphes oprateurs et fonction
nombre variable d'arguments), le spcificateur de format permet donc d'indiquer la taille
de l'objet en ajoutant les attributs suivants avant le caractre de conversion :
hh : indique que l'entier est un [un]signed char au lieu d'un [unsigned] int ;
h : indique que l'entier est de type [unsigned] short au lieu d'un [unsigned] int ;
l : pour les entiers, le type attendu ne sera plus int mais long int et pour les chaines
de caractres, il sera de type wchar_t * (c.f section chanes de caractres).
ll : cet attribut ne concerne que les types entiers, o le type attendu sera long long int.
L : pour les types rels, le type attendu sera long double.
z pour afficher une variable de type size_t.
Pour rsumer les types d'arguments attendus en fonction de l'indicateur de taille et du type
de conversion :

Entres/ sorties

93

Format

Autres attributs
(rarement utiliss)

Attributs de taille

aucun

hh

l (elle)

ll
(elle-elle)

int *

signed
char *

short *

long *

d, i, o, x, X

int

signed
char

short

unsigned
int

unsigned
char

unsigned
short

char *

wchar_t *

int

wint_t

void *

a, A, e, E, f,
F, g, G

double

long long
*

intmax_t *

size_t
*

ptrdiff_t
*

long

long long

intmax_t

size_t

ptrdiff_t

unsigned
long

unsigned
long long

uintmax_t

size_t

ptrdiff_t

long
double

hh et ll sont des nouveauts de C99. On notera qu'avec l'attribut hh et les formats n, d, i,


o, x ou X, le type est signed char et non char. En effet, comme vu dans le chapitre
Types de base, le type char peut tre sign ou non, suivant l'implmentation. Ici, on est sr
de manipuler le type caractre sign.
Quelques exemples :
signed char nb;
printf("%d%hhn", 12345, &nb); /* Affichage de "12345" et nb vaudra 5 */
printf("%ls", L"Hello world!"); /* "Hello world!" */

Arguments positionnels
Il s'agit d'une fonctionnalit relativement peu utilise, mais qui peut s'avrer trs utile dans
le cadre d'une application internationnalise. Considrez le code suivant (tir du manuel de
gettext) :
printf( gettext("La chaine '%s' a %zu caractres\n"), s, strlen(s) );
gettext est un ensemble de fonctions permettant de manipuler des catalogues de langues.
La principale fonction de cette bibliothque est justement gettext(), qui en fonction d'une
chaine de caractre retourne la chaine traduite selon la locale en cours (o celle passe en
argument si rien n'a t trouv).
Une traduction en allemand du message prcdant, pourrait donner : "%d Zeichen lang
ist die Zeichenkette '%s'"
On remarque d'emble que les spcificateurs de format sont inverss par rapport la
chaine originale. Or l'ordre des arguments passs la fonction printf() sera toujours le
mme. Il est quand mme possible de s'en sortir avec les arguments positionnels. Pour cela,
il suffit d'ajouter la suite du caractre % un nombre, suivi d'un signe $. Ce nombre
reprsente le numro de l'argument utiliser pour le spcificateur, en commenant partir
de 1. Un petit exemple :

Entres/ sorties
char * s = "Bla bla";
printf("La chaine %2$s a %1$zu caractres\n", strlen(s), s); /* "La
chaine Bla bla a 7 caractres" */
noter que si un des arguments utilise la rfrence positionnelle, tous les autres
arguments devront faire videmment de mme, sous peine d'avoir un comportement
imprvisible.

criture par bloc ou par ligne


Il s'agit d'une fonctionnalit relativement pointue de la bibliothque stdio, mais qui peut
expliquer certains comportement en apparence trange (notamment avec les systmes
POSIX). Les rglages par dfaut tant bien faits, il y a peu de chance pour que vous ayez
vous soucier de cet aspect, si ce n'est titre de curiosit.
En rgle gnrale les flux de sortie ouvert par via la bibliothque stdio sont grs par
bloc, ce qui veut dire qu'une criture (via printf(), fprintf() ou fwrite()) ne sera pas
systmatiquement rpercute dans le fichier associ.
Cela dpend en fait du type d'objet sur lequel les critures se font :
Un terminal : les critures se feront par ligne, ou si les lignes sont plus grandes qu'une
certaine taille (4Ko en gnral), l'criture se fera par bloc. Les flux en criture seront
aussi purgs si on tente de lire des donnes depuis le mme terminal.
Autre (fichiers, connexion rseau, tubes de communication) : les critures se feront par
bloc, indpendamment des lignes.
Flux d'erreur (stderr) : criture immdiate.
C'est ce qui fait qu'un programme affichant des messages intervalle rgulier (genre une
seconde), affichent ces lignes une une sur un terminal, et par bloc de plusieurs lignes
lorsqu'on redirige sa sortie vers un programme de mise en page (comme more), avec une
latence qui peut s'avrer gnante. C'est ce qui fait aussi qu'une instruction comme
printf("Salut tout le monde"); n'affichera en gnral rien, car il n'y a pas de retour
la ligne.
En fait ce comportement peut tre explicitement rgl, avec cette fonction :
int setvbuf(FILE * restrict flux, char * restrict mem, int mode, size_t
taille);
Cette fonction doit tre appele juste aprs l'ouverture du flux et avant la premire
criture. Les arguments ont la signification suivante :
flux : Le flux stdio pour lequel vous voulez changer la mthode d'criture.
mem : Vous pouvez transmettre une zone mmoire qui sera utilise pour stocker les
donnes avant d'tre crites dans le fichier. Vous pouvez aussi passer la valeur NULL,
dans ce cas les fonctions stdio, appelleront la fonction malloc() lors de la premire
criture.
mode : indique comment se feront les critures :
_IONBF : criture immdiate, pas de stockage temporaire.
_IOLBF : criture par ligne.
_IOFBF : par bloc.
taille : La taille de la zone mmoire transmise ou allouer.

94

Entres/ sorties
La fonction setvbuf() renvoie 0 si elle russi, et une valeur diffrente de zro dans le cas
contraire (en gnral le paramtre mode est invalide).
Cette fonctionnalit peut tre intressante pour les programmes gnrant des messages
sporadiques. Il peut effectivement s'couler un temps arbitrairement long avant que le bloc
mmoire soit plein, si cette commande est redirige vers un autre programme, ce qui peut
s'avrer assez dramatique pour des messages signalant une avarie grave. Dans ce cas, il est
prfrable de forcer l'criture par ligne (ou immdiate), plutt que de faire suivre
systmatiquement chaque criture de ligne par un appel fflush(), avec tous les risques
d'oubli que cela comporte.

Quelques remarques pour finir


La famille de fonctions printf() permet donc de couvrir un large ventail de besoins, au
prix d'une plthore d'options pas toujours faciles retenir.
Il faut aussi faire attention au fait que certaines implmentations de printf() tiennent
compte de la localisation pour les conversions des nombres rels (virgule ou point comme
sparateur dcimal, espace ou point comme sparateurs des milliers, etc.). Ceci peut tre
gnant lorsqu'on veut retraiter la sortie de la commande. Pour dsactiver la localisation, on
peut utiliser la fonction setlocale():
#include <locale.h>
/* ... */
setlocale( LC_ALL, "C" );
printf( ... );
setlocale( LC_ALL, "" );

Entres formates
La bibliothque stdio propose quelques fonctions trs puissantes pour saisir des donnes
depuis un flux quelconque. Le comportement de certaines fonctions (scanf notamment)
peut paraitre surprenant de prime abord, mais s'claircira la lumire des explications
suivantes.
int scanf(const char * restrict format, ...);
int fscanf(FILE * restrict flux, const char * restrict format, ...);
int sscanf(const char * restrict chaine, const char * restrict format,
...);
Ces trois fonctions permettent de lire des donnes formates provenant de :
l'entre standard pour scanf ;
un flux pour fscanf ;
une chane de caractres pour sscanf.
L'argument format ressemble aux rgles d'criture de la famille de fonction printf,
cependant les arguments qui suivent ne sont plus des variables d'entre mais des variables
de sortie (ie : l'appel scanf va modifier leur valeur, il faut donc passer une rfrence).
Ces fonctions retournent le nombre d'arguments correctement lus depuis le format, qui
peut tre infrieur ou gal au nombre de spcificateurs de format, et mme nul.

95

Entres/ sorties

Format de conversion
Les fonctions scanf() analysent le spcificateur de format et les donnes d'entre, en les
comparant caractre caractre et s'arrtant lorsqu'il y en a un qui ne correspond pas.
noter que les blancs (espaces, tabulations et retour la ligne) dans le spcificateur de
format ont une signification spciale : un blanc de la chaine format peut correspondre un
nombre quelconque de blanc dans les donnes d'entre, y compris aucun. D'autres part, il
est possible d'insrer des squences spciales, commenant par le caractre '%' et
l'image de printf(), pour indiquer qu'on aimerait rcuprer la valeur sous la forme dcrite
par le caractre suivant le '%' :
s : extrait la chane de caractres, en ignorant les blancs initiaux et ce jusqu'au
prochain blanc. L'argument correspondant doit tre de type char * et pointer vers un
bloc mmoire suffisamment grand pour contenir la chane et son caractre terminal.
d : extrait un nombre dcimal sign de type int, ignorant les espaces se trouvant
ventuellement avant le nombre.
i : extrait un nombre (de type int) hexadcimal, si la chaine commence par "0x", octal si
la chaine commence par "0" et dcimal sinon. Les ventuels espaces initiaux seront
ignors.
f : extrait un nombre rel, en sautant les blancs, de type float.
u : lit un nombre dcimal non-sign, sans les blancs, de type int.
c : lit un caractre (de type char), y compris un blanc.
[] : lit une chane de caractres qui doit faire partie de l'ensemble entre crochets. Cet
ensemble est une numration de caractre. On peut utiliser le tiret ('-') pour grouper les
dclarations (comme "0-9" ou "a-z"). Pour utiliser le caractre spcial ']' dans
l'ensemble, il doit tre plac en premire position et, pour utiliser le tiret comme un
caractre normal, il doit tre mis la fin. Pour indiquer que l'on veut tous les caractres
sauf ceux de l'ensemble, on peut utiliser le caractre '^' en premire position. noter
que scanf terminera toujours la chane par 0 et que, contrairement au spcificateur %s,
les blancs ne seront pas ignors.
n : Comme pour la fonction printf(), ce spcificateur de format permet de stocker dans
l'entier correspondand de type int, le nombre de caractres lus jusqu' prsent.

Contraindre la largeur
Comme pour la fonction printf(), il est possible de contraindre le nombre de caractres
lire, en ajoutant ce nombre juste avant le caractre de conversion. Dans le cas des chanes,
c'est mme une obligation, dans la mesure o scanf() ne pourra pas ajuster l'espace la
vole.
Exemple :
/* Lit une chane de caractres entre guillemets d'au plus 127
caractres */
char tmp[128];
if (fscanf(fichier, "Config = \"%127[^\"]\"", tmp ) == 1)
{
printf("L'argument associ au mot cl 'Config' est '%s'\n", tmp);
}

96

Entres/ sorties

97

Cet exemple est plus subtil qu'il ne parat. Il montre comment analyser une structure
relativement classique de ce qui pourrait tre un fichier de configuration de type
"MotCl=Valeur". Ce format spcifie donc qu'on s'attend trouver le mot cl "Config", en
ignorant ventuellement les blancs initiaux, puis le caratre '=', entour d'un nombre
quelconque de blancs, eventuellement aucun. la suite de cela, on doit avoir un guillemet
('"'), puis au plus 127 caractres autres que que les guillemets, qui seront stocks dans la
zone mmoire tmp (qui sera termine par 0, d'o l'allocation d'un caractre
supplmentaire). Le guillemet final est l pour s'assurer, d'une part, que la longueur de la
chane est bien infrieure 127 caractre et, d'autre part, que le guillemet n'a pas t
oubli dans le fichier.
En cas d'erreur, on peut par exemple ignorer tous les caractres jusqu' la ligne suivante.

Ajuster le type des arguments


On peut aussi ajuster le type des arguments en fonction des attributs de taille :
Format

Attributs de taille
aucun

hh

l (elle)

ll (elle-elle)

d, i, n

int *

char *

short *

long *

long long *

unsigned int *

unsigned char *

unsigned short * unsigned long *

s, c, [ ]

char *

float *

double *

unsigned long long


*

long double *

Ainsi pour lire la valeur d'un entier sur l'entre standard, on utilisera un code tel que celui
ci :
#include <stdio.h>
int main(void)
{
int i;
printf("Entrez un entier : ");
scanf("%d", &i);
printf("la variable i vaut maintenant %d\n", i);
return 0;
}
Les appels printf ne sont pas indispensables l'excution du scanf, mais permettent
l'utilisateur de comprendre ce qu'attend le programme (et ce qu'il fait aussi).

Entres/ sorties

Conversions muettes
La fonction scanf reconnait encore un autre attribut qui permet d'effectuer la conversion,
mais sans retourner la valeur dans une variable. Il s'agit du caractre toile '*', qui
remplace l'ventuel attribut de taille.
En thorie, la valeur de retour ne devrait pas tenir compte des conversions muettes.
Exemple:
int i, j;
sscanf("1 2.434e-308 2", "%d %*f %d", &i, &j); /* i vaut 1 et j vaut 2
*/

Quelques remarques pour finir


La fonction scanf() n'est pas particulirement adapte pour offrir une saisie conviviale
l'utilisateur, mme en peaufinant l'extrme les spcificateurs de format. En gnral, il
faut s'attendre une gestion trs rudimentaire du clavier, avec trs peu d'espoir d'avoir
ne serait-ce que les touches directionnelles pour insrer du texte un endroit arbitraire.
Qui plus est, lors de saisie de texte, les terminaux fonctionnent en mode bloc : pour que les
donnes soient transmises la fonction de lecture, il faut que l'utilisateur confirme sa saisie
par entre. Mme les cas les plus simple peuvent poser problmes. Par exemple, il arrive
souvent qu'on ne veuille saisir qu'un caractre pour rpondre une question du genre
"craser/Annuler/Arrter ? (o|c|s)", avant d'craser un fichier. Utiliser une des
fonctions de saisie ncessite de saisir d'abord un caractre, ensuite de valider avec la
touche entre. Ce qui peut non seulement tre trs pnible s'il y a beaucoup de questions,
mais aussi risqu si on ne lit les caractres qu'un un. En effet, dans ce dernier cas, si
l'utilisateur entre une chaine de plusieurs caractres, puis valide sa saisie, les caractres
non lus seront disponibles pour des lectures ultrieures, rpondant de ce fait
automatiquement aux questions du mme type.
Il s'agit en fait de deux oprations en apparence simples, mais impossible raliser avec la
bibliothque C standard :
1. Purger les donnes en attente de lecture, pour viter les rponses automatiques .
2. Saisir des caractres sans demande de confirmation.
Ces fonctionnalits sont hlas le domaine de la gestion des terminaux POSIX et spcifies
dans la norme du mme nom.
On l'aura compris, cette famille de fonction est plus l'aise pour traiter des fichiers, ou tout
objet ncessitant le moins d'interaction possible avec l'utilisateur. Nanmoins dans les cas
les plus simples, ce sera toujours un pis-aller.
noter, que contrairement la famille de fonction printf(), scanf() n'est pas sensible
la localisation pour la saisie de nombres rels.

98

Entres/ sorties

Entres non formates


Pour saisir des chaines de caractres indpendemment de leur contenu, on peut utiliser les
fonctions suivantes :
char * fgets(char * restrict chaine, int taille_max, FILE * restrict
flux);
int fgetc( FILE * restrict flux);
int ungetc( int octet, FILE * flux );
La fonction fgets() permet de saisir une ligne complte dans la zone mmoire spcifie,
en vitant tout dbordement. Si la ligne peut tre contenue dans le bloc, elle contiendra le
caractre de saut de ligne ('\n'), en plus du caractre nul. Dans le cas contraire, la ligne
sera tronque, et la suite de la ligne sera obtenue l'appel suivant. Si la fonction a pu lire
au moins un caractre, elle retournera la chaine transmise en premier argument, ou NULL
s'il n'y a plus rien lire.
Un exemple de lecture de ligne arbitrairement longue est fournie dans le livre Exercices en
langage C (nnonc et solution).
La fonction fgetc() permet de ne saisir qu'un caractre depuis le flux spcifi. noter que
la fonction renvoie bien un entier de type int et non de type char, car en cas d'erreur (y
compris la fin de fichier), cette fonction renvoie EOF (dfini -1 en gnral).
noter que cette fonction est incapable de traiter des fichiers mixtes (binaire et texte)
depuis un descripteur en mode caractre (accs squentiel). D'une part la fonction ne
renvoyant pas le nombre d'octets lus (ce qui aurait facilement rgl le problme) et d'autre
part, ftell() ne fonctionnant pas sur de tels descripteurs, il faudra reprogrammer fgets
pour grer ce cas de figure.

Entres/sorties brutes
Les fonctions suivantes permettent d'crire ou de lire des quantits arbitraires de donnes
depuis un flux. Il faut faire attention la portabilit de ces oprations, notamment lorsque
le flux est un fichier. Dans la mesure o lire et crire des structures binaires depuis un
fichier ncessite de grer l'alignement, le bourrage, l'ordre des octets pour les entiers (big
endian, little endian) et le format pour les rels, il est souvent infiniment plus simple de
passer par un format texte.

Sortie
size_t fwrite(const void * buffer, size_t taille, size_t nombre, FILE *
flux);

Entre
size_t fread(void * buffer, size_t taille, size_t nombre, FILE * flux);
Lit nombre lments, chacun de taille taille, partir du flux et stocke le rsultat dans le
buffer. Renvoie le nombre d'lments correctement lus.

99

Entres/ sorties

Gestion des erreurs


Qui dit entres/sorties dit forcment une plthore de cas d'erreurs grer. C'est souvent
ce niveau que se distinguent les bonnes applications des autres : fournir un
comportement cohrent face ces situations exceptionnelles. Dans la description des
fonctions prcdentes, il est fait mention que, en cas d'erreur, un code spcial est retourn
par la fonction. De plus, on dispose de la fonction int ferror( FILE * );, qui permet de
savoir si une erreur a t dclenche sur un fichier lors d'un appel antrieur une fonction
de la bibliothque standard.
Si ces informations permettent de savoir s'il y a eu une erreur, elles ne suffisent pas
connatre la cause de l'ereur. Pour cela, la bibliothque stdio repose sur la variable
globale errno, dont l'utilisation est dcrite dans le chapitre sur la gestion d'erreur.

Erreurs
Le langage C fournit un en-tte spcialis pour la gestion des erreurs : errno.h. Cet en-tte
dclare notamment une variable globale errno, et un certain nombre de codes d'erreur, qui
permettent aux fonctions de la bibliothque standard de reporter prcisment la cause
d'une erreur.

Utilisation
Pour inclure l'en-tte de son fichier source, il faut ajouter la ligne :
#include <errno.h>
On peut alors utiliser la variable errno, de type int, pour traiter les erreurs[1] .
Lorsqu'on veut utiliser errno pour dterminer la cause d'un chec, il faut d'abord s'assurer
que la fonction a bel et bien chou. Le C laissant une certaine libert dans la manire de
signaler un chec, il n'y a pratiquement aucun mcanisme universel qui permette de
dtecter une telle situation, chaque fonction tant presque un cas particulier. Cependant,
une pratique relativement rpandue est de retourner un code spcifique, en gnral en
dehors de l'intervalle de ce qu'on attend. Par exemple, lorsque la fonction est cense
allouer un objet et retourne un pointeur, une erreur est souvent signale en retournant un
pointeur nul, et la variable errno dcrit plus en dtail la nature de l'erreur.
Il est ncessaire de placer errno 0 avant d'utiliser la fonction qui peut chouer, car les
fonctions de la bibliothque standard ne sont pas obliges de la mettre zro en cas de
succs. Si on ne la rinitialisait pas manuellement , on pourrait voir le rsultat d'une
erreur cause par un appel de fonction antrieur. La procdure suivre est donc la
suivante :
Mettre errno 0 ;
Appeler la fonction (de la bibliothque standard) que l'on souhaite ;
Vrifier si elle a chou ;
Si c'est le cas : la valeur de errno est disponible pour traiter l'erreur ;
Sinon : procder la suite du traitement.

100

Erreurs

101

Affichage d'un message d'erreur


Une fois qu'on est sr que la variable errno contient une valeur pertinente, il est
indispensable de dterminer le traitement effectuer, et le cas chant prsenter un
message tout aussi significatif l'utilisateur. Rien n'est plus frustrant pour un utilisateur
qu'un message aussi abscons que : "fopen("/tmp/tmpab560f9d4", "w") : erreur 28",
alors qu'un message crit sous la forme "impossible de crer le fichier
/tmp/tmpab560f9d4: plus de place disponible sur le priphrique" sera plus
facilement compris par l'utilisateur.
numrer toutes les valeurs possibles d'errno pour leur associer un message peut tre
relativement pnible, surtout si on doit le faire dans chaque application. Heureusement
qu'une fonction toute prte existe:
#include <string.h>
char * strerror(int code);
Cette fonction permet de connatre la signification textuelle d'une valeur de errno. noter
que le code de retour est une chaine statique, dont il est sage de prsupposer la dure de
vie la plus courte possible. N'essayez pas non plus de modifier le contenu de la chane,
affichez-la directement ou copiez-la dans une zone temporaire.
Un autre intrt de passer par cette fonction est que les messages retourns sont adapts
la localisation du systme. Ce qui est loin d'tre ngligeable, car cela retire au
programmeur le souci de traduire les messages d'erreurs dus la bibliothque standard
dans toutes les langues possibles (chaque implmentation pouvant avoir ses propres codes
d'erreur spcifiques, la maintenance d'une telle traduction serait un cauchemar).
Une autre fonction, perror, permet d'envoyer le message correspondant errno sur le flux
des erreurs stderr.
#include <errno.h>
void perror(const char *chaine);
Le paramtre chaine, s'il est diffrent de NULL, est une chaine de caractre passe par le
programmeur et affiche avant le message d'erreur.

Exemple
Pour illustrer cela, voici un exemple, qui tente de convertir une chaine de caractres en un
nombre. Le nombre donn, 264, peut ne pas tre dans le domaine de valeur du type
unsigned long (qui peut se limiter 232 - 1), suivant l'architecture. Dans ce cas, on utilise
errno pour afficher un message d'erreur appropri l'utilisateur.
#include
#include
#include
#include
#include

<stdio.h>
<stdlib.h>
<limits.h>
<string.h>
<errno.h>

/*
/*
/*
/*
/*

puts(), printf(), NULL */


strtoul() */
ULONG_MAX */
strerror() */
errno */

static void teste(const char *nombre)


{
unsigned long res;

Erreurs

102

/* On rinitialise errno */
errno = 0;
/* Appel de strtoul : conversion d'une chaine en un entier unsigned
long*/
res = strtoul(nombre, NULL, 10);
/* On dtecte une erreur de strtoul quand elle renvoie ULONG_MAX
_et_
* que errno est non nul. En effet, si on lui passe en entre la
* reprsentation de ULONG_MAX, la conversion se fait sans erreur
* et errno reste 0.
*/
if (res == ULONG_MAX && errno != 0)
{
/* Il y a eu une erreur ! */
(void)fprintf(stderr, "Impossible de convertir le nombre '%s':
%s.\n",
nombre, strerror(errno));
}
else
{
(void)printf("La conversion a t effectue, et la valeur est:
%lu\n", res);
}
}
int main(void)
{
/* 2 puissance 8 : sera toujours accepte */
teste("256");
/* 2^64 - 1 : peut echouer suivant la machine */
teste("18446744073709551615");
return EXIT_SUCCESS;
}
Si le type unsigned long est cod sur 32 bits, on peut obtenir:
La conversion a t effecte, et la valeur est: 256
Impossible de convertir le nombre '18446744073709551615': la valeur est
en dehors de l'intervalle du type spcifi.
Notons bien qu'il est ncessaire de placer errno 0 avant d'utiliser strtoul, car elle
pourrait contenir une valeur non nulle, mme au lancement du programme.

Erreurs

Rfrences
[1] errno peut tre dfinie par une macro ou un identificateur. N'essayez donc pas de rcuprer son adresse.

Mathmatiques
Pour pouvoir utiliser les fonctions mathmatiques, il faut utiliser l'en-tte math, ainsi que
errno.h pour grer les erreurs :
#include <math.h>
#include <errno.h>
Comme pour un certain nombre de fonctions de la biliothques standard, il est en effet
ncessaire d'utiliser errno pour dtecter l'erreur d'une fonction mathmatique (voir le
chapitre sur la gestion d'erreurs pour voir comment utiliser errno).
Note : Sur certains compilateurs comme GCC, il est ncessaire d'ajouter durant l'dition
des liens une option pour que la librairie mathmatique soit lie au programme. Pour
GCC, cette option est -lm. Sans cette option, le programme pourra compiler, mais le
rsultat l'excution sera surprenant...

Exponentiations
double exp ( double x );
double pow ( double x, double y );
exp calcule e lev la puissance de x (
) o e est la base des logarithmes naturels (ln(e)
= 1). pow calcule la valeur de x lv la puissance y (
).

Erreurs
La fonction pow peut dclencher l'erreur suivante :
EDOM : x est ngatif, et y nest pas un entier.

Logarithmes
double log ( double x );
double log10 ( double x );
log calcule le logarithme nperien de x (not gnralement ln(x) en mathmatiques). log10
calcule le logarithme base 10 de x.

Erreurs
EDOM : x est ngatif ;
ERANGE : x est nul.

103

Mathmatiques

Racine carre
double sqrt ( double x );
Renvoie la racine carre de x.

Erreurs
EDOM : x est ngatif.

Sinus, cosinus, tangente


double sin ( double x );
double cos ( double x );
double tan ( double x );
Note : les angles retourns sont en radians (intervalle -pi/2 pi/2).

Arc sinus, arc cosinus


double asin ( double x );
double acos ( double x );

Erreurs
EDOM : x est infrieur -1 ou suprieur 1.

Arc tangente
double atan ( double x );
double atan2 ( double y, double x );

104

Gestion de la mmoire

Gestion de la mmoire
La gestion dynamique de la mmoire en C se fait l'aide de principalement deux fonctions
de la bibliothque standard :
malloc, pour l'allocation dynamique de mmoire;
free, pour la libration de mmoire pralablement alloue avec malloc.
Deux autres fonctions permettent de grer plus finement la mmoire :
calloc, pour allouer dynamiquement de la mmoire, comme malloc, qui a pralablement
t initialise 0;
realloc, pour modifier la taille d'une zone mmoire dj alloue.
Ces fonctions sont dclares dans l'en-tte stdlib.h.

Gestion dynamique de la mmoire


Les dclarations de variables en C et dans beaucoup d'autres langages ont une limitation
trs importante : la taille des variables doit tre connue la compilation. Cela pose
problme quand on ne sait qu' l'excution le nombre de donnes qu'on doit traiter. Pour
rsoudre ce problme, et pouvoir dcider durant l'excution d'un programme du nombre de
variables crer, il faudra ncessairement passer par de l'allocation dynamique de
mmoire. Avec ce systme, le programmeur dispose de fonctions qui permettent de
demander au systme une zone mmoire d'une certaine taille, qu'il pourra utiliser comme il
le souhaite. En C, ces fonctions sont disponibles dans l'en-tte stdlib.h.
L'un des principaux avantages qu'offre le langage C est sa capacit fournir au
programmeur un contrle pouss sur la gestion de la mmoire. Cette libert ncessite
nanmoins une grande rigueur, tant les problmes pouvant survenir sont nombreux et
souvent difficiles diagnostiquer. On peut dire sans prendre beaucoup de risque que la
plupart des erreurs de programmation en C, ont pour origine une mauvaise utilisation des
fonctions de gestion de la mmoire. Il ne faut pas sous-estimer la difficult de cette tche.
Autant cela est trivial pour un programme de quelques centaines de lignes, autant cela peut
devenir un casse-tte quand ledit programme a subi des changements consquents, pas
toujours fait dans les rgles de l'art.
La manire dont la mmoire physique d'un ordinateur est conue, ainsi que la faon dont le
systme d'exploitation la manipule, sont trs variables. Cependant, un modle assez
classique consiste dcouper la mmoire en segments, segments dont on garde les
rfrences dans des tables de pages : c'est le modle de segmentation / pagination. Ce
modle offre beaucoup d'avantages par rapport un accs purement linaire. Dcrire son
fonctionnement en dtail est hors de la port de cet ouvrage, mais on pourra noter tout de
mme :
Indpendance totale de l'espace d'adressage entre les processus : un processus ne peut
pas accder la mmoire d'un autre processus. C'est pourquoi transmettre la valeur d'un
pointeur un autre processus ne servira en gnral rien, car le second processus ne
pourra jamais accder l'emplacement point.
Gestion fine de la mmoire : les segments sont accds via plusieurs niveaux
d'indirection dans les tables de pages. Cela permet de mettre virtuellement les segments
n'importe o dans la mmoire ou mme sur un systme de fichier. Dans la pratique,
parpiller trop les segments (fragmenter) rduit significativement les performances.

105

Gestion de la mmoire
Au niveau des inconvnients, on citera essentiellement un problme de performances.
Plusieurs niveaux d'indirection impliquent de multiple lectures en mmoire, extrmement
pnalisant en terme de temps d'excution, au point o des caches sont ncessaires pour
garantir des vitesses acceptables. Mme si RAM veut dire mmoire accs alatoire, il faut
bien garder l'esprit qu'un accs purement squentiel (adresse mmoire croissante) peut
tre jusqu' cent fois plus rapide qu'une mthode d'accs qui met sans cesse dfaut ce
systme de cache.
Un processus peut donc demander au sytme de rserver pour son usage exclusif un
secteur mmoire de taille dtermine. Il peut galement demander au sytme de modifier
la taille d'une zone prcdemment rserve ou de la librer s'il n'en a plus l'utilit.

Fonctions fournies par stdlib.h


malloc: allocation de mmoire
La fonction la plus simple d'allocation mmoire est malloc :
#include <stdlib.h>
void * malloc(size_t taille);
Elle prend en argument la taille que l'on veut allouer et renvoie un pointeur vers une zone
mmoire alloue ou un pointeur nul si la demande choue (la cause d'erreur la plus
courante tant qu'il n'y a plus assez d'espace mmoire disponible). Trois remarques
peuvent tre faites :
1. malloc renvoie une valeur de type void *, il n'est pas ncessaire de faire une
conversion explicite (cela est ncessaire en C++) ;
2. l'argument taille est de type size_t, l'appel la fonction malloc devrait toujours se
faire conjointement un appel l'oprateur sizeof.
3. La zone mmoire alloue, si l'appel russit, n'est pas initialise. C'est une erreur de
conception grave que d'accder au bloc mmoire en s'attendant trouver une certaine
valeur (0 par exemple).
Par exemple, si on souhaite rserver une zone mmoire pour y allouer un entier:
/* Dclaration et initialisation */
int *ptr = NULL;
/* Allocation */
ptr = malloc(sizeof(int));
/* On vrifie que l'allocation a russi. */
if (ptr != NULL)
{
/* Stockage de la valeur "10" dans la zone mmoire pointe par ptr
*/
*ptr = 10;
}
else
{

106

Gestion de la mmoire
/* dcider du traitement en cas d'erreur */
}
Si on souhaite allouer de l'espace pour une structure de donnes plus complexe :
typedef struct{
int a;
double b;
MaStructure *suivant;
} MaStructure;
/* Dclaration et initialisation */
MaStructure *ptr = NULL;
/* Allocation */
ptr = (MaStructure *)malloc(sizeof(MaStructure));
if (ptr != NULL)
{
/* Initialisation de la structure nouvellement cre */
ptr->a = 10;
ptr->b = 3.1415;
ptr->suivant = NULL;
}

free: libration de mmoire


Le C ne possde pas de mcanisme de ramasse-miettes, la mmoire alloue dynamiquement
par un programme doit donc tre explicitement libre. La fonction free permet de faire
cette libration.
void free( void * pointeur );
La fonction prend en argument un pointeur vers une zone mmoire prcdemment alloue
par un appel malloc, calloc ou realloc et libre la zone mmoire pointe.
Exemple :
/* Dclaration et initialisation */
int *ptr = NULL;
/* Allocation */
ptr = malloc(sizeof(int));
/* On vrifie que l'allocation a russi. */
if (ptr != NULL)
{
/* ... utilisation de la zone alloue ... */
/* Librer la mmoire utilise */
free(ptr);

107

Gestion de la mmoire

108

ptr = NULL; /* Pour viter les erreurs */


}
else
{
/* dcider du traitement en cas d'erreur */
}
L'utilisation du pointeur aprs libration de la zone alloue (ou la double libration d'une
mme zone mmoire) est une erreur courante qui provoque des rsultats imprvisibles. Il
est donc conseill :
d'attribuer la valeur nulle (NULL) au pointeur juste aprs la libration de la zone pointe,
et toute autre pointeur faisant rfrence la mme adresse,
de tester la valeur nulle avant toute utilisation d'un pointeur.
De plus, donner free l'adresse d'un objet qui n'a pas t allou par une des fonctions
d'allocation cause un comportement indfini.

calloc : allocation avec initialisation 0


La fonction calloc permet d'allouer une zone mmoire dont tous les bits seront initialiss
0. Son prototype est lgrement diffrent de celui de malloc, et est pratique pour
l'allocation dynamique de tableaux.
Syntaxe :
void * calloc(size_t nb_element, size_t taille);
De manire similaire malloc, calloc retourne un pointeur de type void* pointant une
zone de nb_element*taille octets alloue en mmoire, dont tous les bits seront initialiss
0, ou retourne un pointeur nul en cas d'chec.
Exemple :
/* allouer un tableau de 5 entiers */
int* ptr = calloc ( 5, sizeof(int) );
Le pointeur contient l'adresse du premier lment du tableau :
*ptr = 3;

/* premier entier : 3

*/

Le pointeur peut tre utilis comme un tableau classique pour accder aux lments qu'il
contient :
ptr[0] = 3; /* quivaut
ptr[1] = 1; /* quivaut
ptr[2] = 4; /* quivaut

*ptr = 3;
*(ptr+1) = 1;
*(ptr+2) = 4;

*/
*/
*/

Notez que calloc place tous les bits zro, mais que ce n'est pas ncessairement une
reprsentation valide pour un pointeur nul ni pour le nombre zro en reprsentation
flottante. Ainsi, pour initialiser zro un tableau de double de manire portable, par
exemple, il est ncessaire d'assigner la valeur 0.0 chaque lment du tableau. tant
donn qu'on initialise chaque lment manuellement , on peut dans ce cas utiliser
malloc plutt que calloc (la premire tant normalement beaucoup plus rapide que la
seconde).

Gestion de la mmoire

109

realloc
Il arrive frquemment qu'un bloc allou n'ait pas la taille suffisante pour accueillir de
nouvelles donnes. La fonction realloc est utilise pour changer (agrandir ou rduire) la
taille d'une zone alloue par malloc, calloc, ou realloc.
Syntaxe :
void * realloc(void * ancien_bloc, size_t nouvelle_taille);
realloc tentera de rajuster la taille du bloc point par ancien_bloc la nouvelle taille
spcifie. noter :
si nouvelle_taille vaut zro, l'appel est quivalent free(ancien_bloc).
si ancien_bloc est nul, l'appel est quivalent malloc(nouvelle_taille).
En cas de succs, realloc alloue un espace mmoire de taille nouvelle_taille, copie le
contenu point par le paramtre pointeur dans ce nouvel espace (en tronquant
ventuellement si la nouvelle taille est infrieure la prcdente), puis libre l'espace
point et retourne un pointeur vers la nouvelle zone mmoire.
En cas d'chec, cette fonction ne libre pas l'espace mmoire actuel, et retourne une
adresse nulle.
Notez bien que realloc ne peut que modifier des espaces mmoires qui ont t allous par
malloc, calloc, ou realloc. En effet, autoriser realloc manipuler des espaces
mmoires qui ne sont pas issus des fonctions de la bibliothque standard pourrait causer
des erreurs, ou des incohrences graves de l'tat du processus. En particulier, les tableaux,
automatiques comme statiques, ne peuvent tre passs realloc, comme illustr par le
code suivant :
Ce code contient une erreur volontaire !
void f(void)
{
int tab[10];
/* ... */
int *ptr = realloc(tab, 20 * sizeof(int));
/* ... */
}
Lorsque realloc reoit la valeur de tab, qui est un pointeur sur le premier lment (i.e.
&tab[0]), il ne peut la traiter, et le comportement est indfini. Sur cet exemple, il est facile
de voir l'erreur, mais dans l'exemple suivant, la situation est plus dlicate :
#include <stdint.h> /* pour SIZE_MAX */
#include <stdlib.h>
/* 'double' essaye de doubler l'espace mmoire point par ptr.
*
* En cas de succs, la valeur renvoye est un pointeur vers le nouvel
espace mmoire, et l'ancienne
* valeur de ptr est invalide.
* En cas d'chec, l'espace point par ptr n'est pas modifi, et la
valeur NULL est renvoye.

Gestion de la mmoire
*/
void *double(void *ptr, size_t n)
{
void *tmp = NULL;
if ((ptr != NULL) && (n != 0) && (n <= SIZE_MAX / 2))
{
tmp = realloc(ptr, 2 * n);
}
return tmp;
}
La fonction double en elle-mme ne comporte pas d'erreur, mais elle peut causer des
plantages suivant la valeur de ptr qui lui est passe. Pour viter des erreurs, il faudrait
que la documentation de la fonction prcise les contraintes sur la valeur de ptr... et que les
programmeurs qui l'utilisent y fassent attention.
On peut aussi noter que, quand realloc russit, le pointeur renvoy peut trs bien tre
gal au pointeur initial, ou lui tre diffrent. En particulier, il n'y a aucune garantie que,
quand on diminue la taille de la zone mmoire, il le fasse sur place . C'est trs probable,
car c'est ce qui est le plus facile et rapide faire du point de vue de l'implmentation, mais
rien ne l'empche par exemple de chercher un autre espace mmoire disponible qui aurait
exactement la taille voulue, au lieu de garder la zone mmoire initiale.
On peut noter le test (n <= SIZE_MAX / 2). Il permet d'viter un dbordement entier : si n
tait suprieur cette valeur, le produit n * 2 devrait avoir une valeur suprieure
SIZE_MAX, qui est la plus grande valeur reprsentable par le type size_t. Lorsque cette
valeur est passe realloc, la conversion en size_t, qui est un type entier non sign, se
fera modulo SIZE_MAX + 1, et donc la fonction recevra une valeur diffrente de celle
voulue. Si le test n'tait pas fait, double pourrait ainsi retourner l'appelant une zone
mmoire de taille infrieure celle demande, ce qui causerait des bogues. Ce genre de
bogue (non spcifique realloc) est trs difficile dtecter, car n'apparat que lorsque
l'on atteint des valeurs limites, ce qui est assez rare, et le problme peut n'tre visible que
bien longtemps aprs que l'erreur de calcul soit faite.
Gestion d'erreur
Pour grer correctement le cas d'chec, on ne peut faire ainsi:
Ce code contient une erreur volontaire !
int *ptr = malloc(10 * sizeof(int));
if (ptr != NULL)
{
/* ... */
/* On se rend compte qu'on a besoin d'un peu plus de place. */
ptr = realloc(ptr, 20 * sizeof(int));
/* ... */
}
En effet, si realloc choue, la valeur de ptr est alors nulle, et on aura perdu la rfrence
vers l'espace de taille 10 * sizeof(int) qu'on a dj allou. Ce type d'erreur s'appelle

110

Gestion de la mmoire

111

une fuite mmoire. Il faut donc faire ainsi:


int *ptr = malloc(10 * sizeof(int));
if (ptr != NULL)
{
/* ... */
/* On se rend compte qu'on a besoin d'un peu plus de place. */
int *tmp = realloc(ptr, 20 * sizeof(int));
if (tmp == NULL)
{
/* Exemple de traitement d'erreur minimaliste */
free(ptr);
return EXIT_FAILURE;
}
else
{
ptr = tmp;
/* On continue */
}
}
Exemple
realloc peut tre utilis quand on souhaite boucler sur une entre dont la longueur peut
tre indfinie, et qu'on veut grer la mmoire assez finement. Dans l'exemple suivant, on
suppose dfinie une fonction lire_entree qui :
lit sur une entre quelconque (par exemple stdin) un entier, et renvoie 1 si la lecture
s'est bien passe ;
renvoie 0 si aucune valeur n'est prsente sur l'entre, ou en cas d'erreur.
Cette fonction est utilise pour construire un tableau d'entiers issus de cette entre.
Comme on ne sait l'avance combien d'lments on va recevoir, on augmente la taille d'un
tableau au fur et mesure avec realloc.
/* Fonction qui utilise 'lire_entree' pour lire un nombre indtermin
* d'entiers.
* Renvoie l'adresse d'un tableau d'entiers (NULL si aucun entier n'est
lu).
* 'taille' est plac au nombre d'lments lus (ventuellement 0).
* 'erreur' est place une valeur non nulle en cas d'erreur, une
valeur nulle sinon.
*/
int *traiter_entree(size_t *taille, int *erreur)
{
size_t max = 0;
/* Nombre d'lments utilisables */
size_t i = 0;
/* Nombre d'lments utiliss */
int *ptr = NULL; /* Pointeur vers le tableau dynamique */
int valeur;
/* La valeur lue depuis l'entre */

Gestion de la mmoire

112

*erreur = 0;
while (lire_entree(&valeur) != 0)
{
if (i >= max)
{
/* Il n'y a plus de place pour stocker 'valeur' dans
'ptr[i]' */
max = max + 10;
int *tmp = realloc(ptr, max * sizeof(int));
if (tmp == NULL)
{
/* realloc a chou : on sort de la boucle */
*erreur = 1;
break;
}
else
{
ptr = tmp;
}
}
ptr[i] = valeur;
i++;
}
*taille = i;
return ptr;
}
Ici, on utilise max pour se souvenir du nombre d'lments que contient la zone de mmoire
alloue, et i pour le nombre d'lments effectivement utiliss. Quand on a pu lire un entier
depuis l'entre, et que i vaut max, on sait qu'il n'y a plus de place disponible et qu'il faut
augmenter la taille de la zone de mmoire. Ici, on incrmente la taille max de 10 chaque
fois, mais il est aussi possible de la multiplier par 2, ou d'utiliser toute autre formule. On
utilise par ailleurs le fait que, quand le pointeur envoy realloc est nul, la fonction se
comporte comme malloc.
Le choix de la formule de calcul de max utiliser chaque fois que le tableau est rempli
rsulte d'un compromis :
augmenter max peu peu permet de ne pas gaspiller trop de mmoire, mais on appellera
realloc trs souvent.
augmenter trs vite max gnre relativement peu d'appels realloc, mais une grande
partie de la zone mmoire peut tre perdue.
Une allocation mmoire est une opration qui peut tre coteuse en terme de temps, et un
grand nombre d'allocations mmoire peut fractionner l'espace mmoire disponible, ce qui
alourdit la tche de l'allocateur de mmoire, et au final peut causer des pertes de
performance de l'application. Aucune formule n'est universelle, chaque situation doit tre
tudie en fonction de diffrents paramtres (systme d'exploitation, capacit mmoire,

Gestion de la mmoire
vitesse du matriel, taille habituelle/maximale de l'entre...). Toutefois, l'essentiel est bien
souvent d'avoir un algorithme qui marche, l'optimisation tant une question secondaire.
Dans une telle situation, utilisez d'abord une mthode simple (incrmentation ou
multiplication par une constante), et n'en changez que si le comportement du programme
devient gnant.

Problmes et erreurs classiques


Dfaut d'initialisation d'un pointeur
Pour viter des erreurs, un pointeur devrait toujours tre initialis lors de sa dclaration;
soit NULL, soit avec l'adresse d'un objet, soit avec la valeur de retour d'une fonction
sre comme malloc. Mditons sur l'exemple suivant:
Ce code contient une erreur volontaire !
/* Dclaration sans initialisation */
int *ptr;
int var;
/* On stocke dans b la valeur de la zone mmoire pointe par ptr*/
var = *ptr;
Ce code va compiler! Si on est chanceux l'excution de ce code provoquera une erreur la
troisime tape lorsqu'on drfrence ptr. Si on l'est moins, ce code s'excute sans souci et
stocke dans var une valeur alatoire, ce qui pourrait provoquer une erreur lors d'une
utilisation ultrieure de la variable var alors que l'erreur relle se situe bien plus en amont
dans le code! Une variable (automatique) de type pointeur est en effet comme toutes les
autres variables: si elle n'est pas initialise explicitement, son contenu est indfini; son
drfrencement peut donc causer n'importe quel comportement.
Lorsqu'on dclare une variable ou qu'on alloue une zone mmoire, on ne dispose d'aucune
garantie sur le contenu de cette zone ou de cette variable. Initialiser systmatiquement les
variables ds leur dclaration, en particulier lorsqu'il s'agit de pointeurs, fait partie des
bons rflexes prendre et pour la plupart des applications ne dgrade pas le temps
d'excution de manire significative. Ce type d'erreur peut tre extrmement difficile
localiser l'intrieur d'un programme plus complexe.

Rfrence multiple une mme zone mmoire


La recopie d'un pointeur dans un autre n'alloue pas une nouvelle zone. La zone est alors
rfrence par deux pointeurs. Un problme peut survenir si on libre la zone alloue sans
rinitialiser tous les pointeurs correspondants :
Ce code contient une erreur volontaire !
int* ptr = malloc(sizeof(int)); // allouer une zone mmoire pour un
entier
int* ptr2 = ptr; // ptr2 pointe la mme zone mmoire
*ptr = 5;
printf("%d\n", *ptr2 ); // affiche 5

113

Gestion de la mmoire
/* libration */
free( ptr );
ptr = NULL;
*ptr2 = 10; /* <- rsultat imprvisible (plantage de l'application, ...) */
En rgle gnrale, il faut viter que plusieurs variables pointent la mme zone mmoire
alloue.
L'utilisation d'un outil de vrification statique permet de dtecter ce genre d'erreur.

Fuite mmoire
La perte du pointeur associ un secteur mmoire rend impossible la libration du secteur
l'aide de free. On qualifie cette situation de fuite mmoire, car des secteurs demeurent
rservs sans avoir t dsallous. Cette situation perdure jusqu' la fin du programme
principal. Voyons l'exemple suivant:
Ce code contient une erreur volontaire !
int i = 0;
// un compteur
int* ptr = NULL; // un pointeur
while (i < 1001) {
ptr = malloc(sizeof(int)); // on crase la valeur prcdente de ptr
par une nouvelle
if (ptr != NULL)
{
/* traitement ....*/
}
}
la sortie de cette boucle on a allou un millier d'entiers soit environ 2000 octets, que l'on
ne peut pas librer car le pointeur ptr est cras chaque itration (sauf la dernire). La
mmoire disponible est donc peu peu grignote jusqu'au dpassement de sa capacit. Ce
genre d'erreurs est donc proscrire pour des processus tournant en boucle pendant des
heures, voire indfiniment. Si son caractre cumulatif la rend difficile dtecter, il existe
de nombreux utilitaires destins traquer la moindre fuite.

114

Gestion des signaux

Gestion des signaux


bauche
Cette page est considre comme une bauche complter. Si vous possdez
quelques connaissances sur le sujet, vous pouvez les partager en ditant ds
prsent cette page (en cliquant sur le lien C/Gestion des signaux modifier
[1]
).
Ressources suggres : Aucune (vous pouvez indiquer les ressources que vous suggrez qui
pourraient aider d'autres personnes complter cette page dans le paramtre ressources
du modle? engendrant ce cadre)
Les signaux permettent une communication, assez sommaire, entre le systme et un
processus, ou entre diffrents processus. Cette communication est sommaire, car un signal
ne porte qu'une seule information: son numro, de type int. Un processus peut aussi
s'envoyer un signal lui-mme.
Ces signaux sont envoys de manire asynchrone: lorsqu'un processus reoit un signal, son
excution est interrompue, et une fonction spcifique, dite gestionnaire de signal, est
appele, avec en paramtre le numro du signal reu, pour traiter l'vnement. Lorsque
cette fonction se termine, le processus reprend l o il s'tait arrt.
C'est chaque processus de dterminer ce qu'il fait quand il reoit un signal de numro
donn, en dfinissant un gestionnaire de signal pour tous les signaux qu'il le souhaite. Vous
l'aurez remarqu, dans tous les exemples de ce livre rencontrs jusqu' prsent, nous ne
nous sommes pas occups de savoir quels signaux nous pourrions recevoir, et comment il
fallait les traiter. En l'absence de prcisions dans nos programmes, l'implmentation
fournira en effet un gestionnaire de signal par dfaut, qui le plus souvent se contentera
d'arrter le programme.
Les fonctions suivantes, fournies par l'en-tte signal.h, sont utilises dans la gestion des
signaux :
signal() pour dfinir un gestionnaire de signal;
et raise() pour envoyer un signal au processus courant.
Le C dfinit ces deux fonctions, et pas plus, alors que les signaux peuvent faire beaucoup
plus... En particulier, aucune fonction pour envoyer un signal un autre processus n'est
dfinie. Cela est d au fait que, sur certains systmes, la notion de processus n'existe pas,
et une seule tche s'excute. Dans de telles situations, il serait aberrant de demander un
compilateur C pour un tel systme de fournir des moyens de communications
inter-processus ! Par contre, les communications inter-processus tant chose courante dans
de nombreux autres systmes, des extensions fournissent des moyens de le faire (voir par
exemple la fonction kill() dfinie dans POSIX, cf. le chapitre Gestion des signaux du livre
Programmation POSIX).

115

Gestion des signaux

Dfinir un gestionnaire de signal


La fonction
void (*signal(int sig, void (*func)(int)))(int);
permet de dfinir un gestionnaire de signal. La signature de cette fonction tant un peu
complexe, on peu la simplifier en utilisant un typedef:
typedef void (*t_handler)(int);
t_handler signal(int sig, t_handler func);
Ainsi, on voit mieux comment fonctionne cette fonction (notez que le type t_handler n'est
pas dfini par la norme, et n'est ici que pour clarifier la syntaxe). Elle prend deux
paramtres:
le numro du type de signal traiter;
et un pointeur vers la fonction de gestion du signal.
Le premier paramtre est le numro du signal. La norme dfinit un faible nombre de
signaux, par des macros dans l'en-tte signal.h, par exemple SIGSEGV qui indique un
accs illgal la mmoire (SEGmentation Violation), et laisse l'implmentation la libert
de dfinir d'autres types de signaux.
Pour le deuxime paramtre, on peut donner trois valeurs possibles:
SIG_IGN (macro dfinie dans signal.h): tout signal ayant le numro sig sera ignor (i.e.
rien ne se passera, le programme continue de s'excuter);
SIG_DFL (id.): le gestionnaire de signal par dfaut sera mis en place pour ce type de
signal.
un pointeur vers une fonction de type t_handler: cette fonction sera appele lorsqu'un
signal de type sig sera reu par le processus.
Cette fonction renvoie la valeur du dernier gestionnaire de signal pour le numro donn
(qui peut tre SIG_IGN ou SIG_DFL).
En cas d'erreur, la fonction renvoie SIG_ERR et place la variable errno une valeur
significative.

Les gestionnaires de signaux


Les gestionnaires de signaux sont des fonctions au prototype simple :
void fonction(int);
Elles prennent un argument de type int, qui est le numro du signal, et ne renvoient rien.
En effet, ces fonctions tant appeles de manire asynchrone, leur ventuelle valeur de
retour ne serait rcupre, et encore utilise, par personne...
Comme on utilise signal pour associer chaque numro de signal une fonction qui va
grer les signaux de ce numro, on pourrait penser qu'il n'est pas ncessaire de donner ce
numro en paramtre la fonction. Cependant cela s'avre pratique, car on peut ainsi
dfinir une seule fonction qui peut grer plusieurs types de signaux, et dont le
comportement variera en fonction du signal traiter.
Voici un exemple simple de dfinition et de mise en place d'un gestionnaire de signaux,
pour le type de signal SIG_FPE, qui concerne des exceptions lors des calculs en virgule

116

Gestion des signaux


flottante (Floating Point Exception):
#include <signal.h>
#include <stdio.h>
void sig_fpe(int sig)
{
/* ... */
}
int main(void)
{
if (signal(SIG_FPE, sig_fpe) == SIG_ERR)
{
puts("Le gestionnaire de signal pour SIG_FPE n'a pu etre
defini.");
}
else
{
puts("Le gestionnaire de signal pour SIG_FPE a pu etre
defini.");
}
/* ... */
return 0;
}

Rfrences
[1] http:/ / en. wikipedia. org/ wiki/ Programmation

117

Conclusion

Conclusion
Le C est un langage plein de paradoxes. Pouvant aussi bien s'accommoder d'applications
bas niveau que proposer des interfaces relativement proches des mthodes orientes
objets, ce langage a de quoi sduire un large public. Pourtant, force est de constater qu'il a
fait souffrir beaucoup de personnes et pas seulement les programmeurs. Issu de l'poque
o la mmoire et les capacits de calcul taient encore des denres rares et chres, les
pionniers de la programmation ont trop souvent privilgi le ct bas niveau de ce langage.
Il en rsulta des applications difficilement maintenables, abusant des aspects les plus
complexes ou non-portables qu'offre le C pour conomiser le moindre cycle processeur.
C'est de cette poque que sa rputation d'assembleur plus compliqu et plus lent que
l'assembleur s'est forge, clipsant ses aspects de plus haut niveau, qui pourtant mritent
une meilleure estime.
Les fonctions de la bibliothque standard et surtout le langage lui-mme contiennent
beaucoup de piges , notamment par les comportements indfinis, ou dpendants de
l'implmentation, qui rendent des bogues parfois trs difficiles dcouvrir. Le
programmeur doit comprendre que le C lui fournit de grandes possibilits, mais qu'en
retour il lui demande une rigueur d'autant plus grande. Apprivoiser ce langage peut
ncessiter une longue phase d'apprentissage. Nous esprons que cet ouvrage vous aura
permis d'apprhender le C avec plus de srnit et de casser certains mythes qui ont
dcidment la vie bien dure.
Il existe beaucoup de bibliothques qui proposent au programmeur C des outils pour
tendre la bibliothque standard, et permettent ainsi la gestion d'interfaces graphiques, la
programmation rseau, etc. Celles-ci sont trs riches, et bien trop nombreuses pour tre
abordes dans ce livre. Wikilivres propose des livres tudiant certaines de ces
bibliothques. Pour les autres, Internet et ses moteurs de recherche restent de loin les
meilleurs outils pour trouver ce dont vous aurez ncessairement besoin.

118

Bibliographie

119

Bibliographie
Vous trouverez ici des rfrences d'ouvrages ou de sites internet ayant servi l'laboration
de ce wikilivre, ou pouvant tre d'intrt pour une connaissance plus approfondie du C.

Site officiel du WG14 et rfrences normatives


Le site officiel du WG14 [1] (en). Contient de nombreuses informations sur le C, dont les
dernires propositions en discussion pour l'volution de la norme. Attention toutefois, ces
documents sont globalement trs techniques. On peut noter la prsence du Rationale
pour la norme C99, qui lui est abordable.
Le TC3 n'est pas disponible sur le site du WS, mais sur celui de l'ISO [2].
Rationale [3] de la norme ANSI X3.159-1989 (en) (absent du site du WG14)
La page [4] du wiki du groupe de discussion comp.lang.c rfrenant les documents
normatifs et les moyens d'acqurir la norme (en).

Livres
Le K&R :
Brian W. Kernighan et Dennis M. Ritchie. Le Langage C: Norme ANSI, 2e d., Dunod,
(ISBN 2-100-05116-4) http://c.developpez.com/livres/#L2100487345 prsentation en ligne

(en) Brian W. Kernighan et Dennis M. Ritchie. The C Programming Language, Second


Edition. Prentice Hall, Inc., 1988. (ISBN 0-13-110362-8) http://cm.bell-labs.com/cm/cs/cbook/
prsentation en ligne

Le site Developpez.com [5] propose des critiques de livres sur le C ici


aussi des livres sur le C++).

[6]

(la page contient

Compilateurs C
Le wiki de comp.lang.c liste ici [7] un certain nombre de compilateurs en prcisant leur
support des normes C90 et C99 (en).

Autres ressources
Tutoriels
Les bases du langage C

[8]

par William Waisse.

Histoire du C
The Development of the C Language [9] par Dennis Ritchie (en).
Historique de C [10] par Marc Mongenet.

Bibliographie

Rfrences
[1] http:/ / www. open-std. org/ jtc1/ sc22/ wg14/
[2] http:/ / www. iso. org/ iso/ iso_catalogue/ catalogue_tc/ catalogue_detail. htm?csnumber=50510
[3] http:/ / www. lysator. liu. se/ c/ rat/ title. html
[4] http:/ / clc-wiki. net/ wiki/ C_standardisation:ISO
[5] http:/ / www. developpez. com
[6] http:/ / c. developpez. com/ livres
[7] http:/ / clc-wiki. net/ wiki/ C_resources:Compilers
[8] http:/ / neofutur. net/ langage_c/ examples_language_C/ langage_C_les_bases/ langage_C_bases. html
[9] http:/ / cm. bell-labs. com/ cm/ cs/ who/ dmr/ chist. html
[10] http:/ / marc. mongenet. ch/ Articles/ C/ index. html

120

Sources et contributeurs de l'article

Article Sources and Contributors


Avant- propos Source: http://fr.wikibooks.org/w/index.php?oldid=235462 Contributeurs: Alveric, Sub, 9 modifications anonymes
Sommaire Source: http://fr.wikibooks.org/w/index.php?oldid=143853 Contributeurs: Alveric, Merrheim, Tavernier, Tpierron, 15 modifications
anonymes
Introduction Source: http://fr.wikibooks.org/w/index.php?oldid=224700 Contributeurs: Alveric, Dhenry, Guillaumito, Jean-Jacques MILAN, Marc
Mongenet, Merrheim, Pfv2, Sigma 7, Spartan, Sub, Tavernier, Tpierron, 47 modifications anonymes
Bases du langage Source: http://fr.wikibooks.org/w/index.php?oldid=237666 Contributeurs: Acieroid, Alveric, DavidL, Dhenry, Guillaumito, Pfv2,
Romanito, Sigma 7, Tados, Tavernier, Thierry46, Tpierron, Traroth, 58 modifications anonymes
Types de base Source: http://fr.wikibooks.org/w/index.php?oldid=237667 Contributeurs: Alveric, DavidL, Dhenry, Diapiser, Emmanuel Delahaye,
Guillaumito, Marc Mongenet, Sigma 7, Tados, Tavernier, Thierry46, Tpierron, Traroth, 44 modifications anonymes
Classe de stockage Source: http://fr.wikibooks.org/w/index.php?oldid=237662 Contributeurs: Acieroid, Alveric, Dhenry, Guillaumito, HiroshimaCC,
Tados, Thierry46, Tpierron, 24 modifications anonymes
Oprateurs Source: http://fr.wikibooks.org/w/index.php?oldid=237645 Contributeurs: Alveric, DavidL, Dhenry, Ecureuil gris, Guillaumito, Marc
Mongenet, Tados, Tpierron, 29 modifications anonymes
Structures de contrle - tests Source: http://fr.wikibooks.org/w/index.php?oldid=236372 Contributeurs: Alveric, Dhenry, Guillaumito, Tpierron, 11
modifications anonymes
Structures de contrle - itrations Source: http://fr.wikibooks.org/w/index.php?oldid=236402 Contributeurs: Alveric, Dhenry, Guillaumito,
Thierry46, Tpierron, 16 modifications anonymes
Fonctions et procdures Source: http://fr.wikibooks.org/w/index.php?oldid=237665 Contributeurs: Alveric, DavidL, Dhenry, Francois Trazzi,
Guillaumito, Tados, Thierry46, Tpierron, 27 modifications anonymes
Tableaux Source: http://fr.wikibooks.org/w/index.php?oldid=203574 Contributeurs: Alveric, Dhenry, Guillaumito, Marc Mongenet, Sub, Thierry46,
Tpierron, 21 modifications anonymes
Pointeurs Source: http://fr.wikibooks.org/w/index.php?oldid=182308 Contributeurs: Alveric, DavidL, Dhenry, Guillaumito, Marc Mongenet, Tavernier,
Tpierron, 34 modifications anonymes
Types avancs - structures, unions, numrations Source: http://fr.wikibooks.org/w/index.php?oldid=152271 Contributeurs: Alveric, Dhenry,
Ecureuil gris, Guillaumito, Tavernier, Tpierron, 26 modifications anonymes
Prprocesseur Source: http://fr.wikibooks.org/w/index.php?oldid=228570 Contributeurs: Alveric, DavidL, Dhenry, Greudin, Guillaumito, Haypo,
Tpierron, 27 modifications anonymes
Bibliothque standard Source: http://fr.wikibooks.org/w/index.php?oldid=153829 Contributeurs: Alveric, Archibald, Grunge, Sub, Thierry46,
Tpierron, 2 modifications anonymes
Chanes de caractres Source: http://fr.wikibooks.org/w/index.php?oldid=154917 Contributeurs: Alveric, Dhenry, Francois Trazzi, Guillaumito,
Thierry46, Tpierron, 21 modifications anonymes
Entres/ sorties Source: http://fr.wikibooks.org/w/index.php?oldid=191661 Contributeurs: Alveric, Dhenry, Greudin, Guillaumito, Romanito,
Thierry46, Tpierron, 57 modifications anonymes
Erreurs Source: http://fr.wikibooks.org/w/index.php?oldid=224812 Contributeurs: Alveric, Francois Trazzi, Leszek.hanusz, Thierry46, Tpierron, 6
modifications anonymes
Mathmatiques Source: http://fr.wikibooks.org/w/index.php?oldid=235215 Contributeurs: Alveric, Guillaumito, Jean-Jacques MILAN, Tpierron, 9
modifications anonymes
Gestion de la mmoire Source: http://fr.wikibooks.org/w/index.php?oldid=183268 Contributeurs: Aloux, DavidL, Sanao, Thierry46, 35 modifications
anonymes
Gestion des signaux Source: http://fr.wikibooks.org/w/index.php?oldid=140715 Contributeurs: DavidL, Greudin, 10 modifications anonymes
Conclusion Source: http://fr.wikibooks.org/w/index.php?oldid=117541 Contributeurs: Alveric, Guillaumito, Jean-Jacques MILAN, Romanito, Tpierron, 7
modifications anonymes
Bibliographie Source: http://fr.wikibooks.org/w/index.php?oldid=218475 Contributeurs: 8 modifications anonymes

121

Source des images, licences et contributeurs

Image Sources, Licenses and


Contributors
Image:Ken n dennis.jpg Source: http://fr.wikibooks.org/w/index.php?title=Fichier:Ken_n_dennis.jpg Licence: Public Domain Contributeurs: Image:Achtung.svg Source: http://fr.wikibooks.org/w/index.php?title=Fichier:Achtung.svg Licence: Public Domain Contributeurs: see below.
Image:Nuvola apps korganizer.png Source: http://fr.wikibooks.org/w/index.php?title=Fichier:Nuvola_apps_korganizer.png Licence: inconnu
Contributeurs: Image:Puzzle.svg Source: http://fr.wikibooks.org/w/index.php?title=Fichier:Puzzle.svg Licence: GNU Free Documentation License Contributeurs:
User:Ganeshk

122

Licence

Licence
Creative Commons Attribution-Share Alike 3.0 Unported
http:/ / creativecommons. org/ licenses/ by-sa/ 3. 0/

123

Vous aimerez peut-être aussi