Vous êtes sur la page 1sur 35

Le Langage C

Version 1.2
c
2002
Florence HENRY
Observatoire de Paris Universit de Versailles
florence.henry@obspm.fr

Table des matires


1

Les bases

Variables et constantes

Quelques fonctions indispensables

Les instructions de contrle

11

Les fonctions

15

Les tableaux

18

Les structures

20

Les pointeurs

22

Pointeurs et fonctions

25

10 Pointeurs et tableaux

26

11 Allocation dynamique de mmoire

31

Chapitre 1
Les bases
1.1

La structure dun programme

Un programme simple se compose de plusieurs parties :


des directives de prcompilation
une ou plusieurs fonctions dont lune sappelle obligatoirement main(), celle-ci constitue le
programme principal et comporte 2 parties :
la dclaration de toutes les variables et fonctions utilises
des insctructions
Les commentaires dbutent par /* et finissent par */, ils peuvent stendre sur plusieurs lignes.

1.1.1

Les directives de prcompilation

Elles commencent toutes par un #.


commande
#include <stdio.h>
#include <math.h>
#define PI 3.14159
#undef PI
#ifdef PI
instructions 1 ...
#else
instructions 2 ...
#endif

signification
permet dutiliser les fonctions printf() et scanf()
permet dutiliser les fonctions mathmatiques
dfinit la constante PI
partir de cet endroit, la constante PI nest plus dfinie
si la constante PI est dfinie, on compile les instructions 1,
sinon, les instructions 2

Parmi ces directives, une seule est obligatoire pour le bon fonctionnement dun programme :
#include <stdio.h> . En effet, sans elle, on ne peut pas utiliser les fonctions utiles pour laffichage
lcran : printf() et la lecture des donnes au clavier : scanf(). Nous verrons le fonctionnement
de ces fonctions plus tard.

1.1.2 La fonction main()


Elle commence par une accolade ouvrante { et se termine par une accolade fermante }. lintrieur, chaque instruction se termine par un point-virgule. Toute variable doit tre dclare.

main(){
int i ; /* declaration des variables */
instruction_2 ;
...
}

instruction_1 ;

Exemple de programme simple :


#include <stdio.h>
/* Mon 1er programme en C */
main(){
printf("Hello world\n") ;
}

1.2

La compilation sous Unix

Une fois le programme crit, on ne peut pas lexcuter directement. Pour que lordinateur comprenne ce que lon veut lui faire faire, il faut traduire le programme en langage machine. Cette
traduction sappelle la compilation.
On compile le programme par la commande cc prog.c, o prog.c est le nom du programme. La
compilation cre un fichier excutable : a.out. On peut vouloir lui donner un nom plus explicite, et
pour cela, la compilation, on compile avec la commande cc -o prog prog.c qui va appeler le
programme excutable prog au lieu de a.out.
On dmarre alors le programme avec la commande ./prog.

Chapitre 2
Variables et constantes
2.1

Les constantes

Constantes entires 1,2,3,...


Constantes caractres a,A,...
Constantes chanes de caractres "Bonjour"
Pas de constantes logiques Pour faire des tests, on utilise un entier. 0 est quivalent a faux et tout ce
qui est 6= 0 est vrai.

2.2
2.2.1

Les variables
Noms des variables

Le C fait la diffrence entres les MAJUSCULES et les minuscules. Donc pour viter les confusions, on crit les noms des variables en minuscule et on rserve les majuscules pour les constantes
symboliques dfinies par un #define. Les noms doivent commencer par une lettre et ne contenir
aucun blanc. Le seul caractre spcial admis est le soulignement (_). Il existe un certain nombre de
noms rservs (while, if, case, ...), dont on ne doit pas se servir pour nommer les variables. De plus,
on ne doit pas utiliser les noms des fonctions pour des variables.

2.2.2

Dclaration des variables

Pour dclarer une variable, on fait prcder son nom par son type.
Il existe 6 types de variables :
type
char
short
int
long
float
double

signification
caractre cod sur 1 octet (8 bits)
entier cod sur 1 octet
entier cod sur 4 octets
entier cod sur 8 octets
rel cod sur 4 octets
rel cod sur 8 octets

val. min
27
27
231
263
1038
10308

val. max
27 1
27 1
231 1
263 1
1038
10308

On peut faire prcder chaque type par le prfixe unsigned, ce qui force les variables prendre
des valeurs uniquement positives.
Exemples de dclarations :

dclaration
int a ;
int z=4 ;
unsigned int x ;
float zx, zy ;
float zx=15.15 ;
double z ;
char zz ;
char zz=a ;

signification
a est entier
z est entier et vaut 4
x est un entier positif (non sign)
zx et zy sont de type rel
zx est de type rel et vaut 15.15
z est un rel en double prcision
zz est une variable caractre
zz vaut a

Il ny a pas de type complexe.

2.3

Les oprateurs

Le premier oprateur connatre est laffectation "=". Exemple : {a=b+" ;} Il sert mettre dans
la variable de gauche la valeur de ce qui est droite. Le membre de droite est dabord valu, et
ensuite, on affecte cette valeur la variable de gauche. Ainsi linstruction i=i+1 a un sens.
Pour les oprations dites naturelles, on utilise les oprateurs +, -, *, /, %.
% est lopration modulo : 5%2 est le reste de la division de 5 par 2. 5%2 est donc gal 1.
Le rsultat dune opration entre types diffrents se fait dans le type le plus haut. Les types sont classs
ainsi :
char < int < float < double
Par ailleurs, lopration a+1 a un sens, elle a pour rsultat le caractre suivant a dans le code
ASCII.
En C, il existe un certain nombre doprateurs spcifiques, quil faut utiliser prudemment sous
peine derreurs.
++ incrmente la variable dune unit.
- - dcrmente la variable dune unit.
Ces 2 oprateurs ne sutilisent pas avec des rels. Exemples dutilisation :
i++ ; /* effectue i=i+1 */
i- - ; /* effectue i=i-1 */
Leur utilisation devient dlicate quand on les utilise avec dautres oprateurs. Exemple :
int i=1 , j ;
j=i++ ;
j=++i ;

/*
/*
/*
/*

effectue dabord j=i et ensuite i=i+1 */


on a alors j=1 et i=2 */
effectue dabord i=i+1 et ensuite j=i */
on a alors j=2 et i=2 */

Quand loprateur ++ est plac avant une variable, lincrmentation est effectue en premier. Lincrmentation est faite en dernier quand ++ est plac aprs la variable. Le comportement est similaire pour
- -.

Dautres oprateurs sont dfinis dans le tableau qui suit :


i+=5 ;
i-=3 ;
i*=4 ;
i/=2 ;
i%=3 ;

/*
/*
/*
/*
/*

i=i+5
i=i-3
i=i*4
i=i/2
i=i%3

*/
*/
*/
*/
*/

Pour finir, ajoutons que les oprateurs qui servent comparer 2 variables sont :
==
<
>
&&

gal
infrieur
suprieur
et logique

!=
<=
>=
||

diffrent de
infrieur ou gal
suprieur ou gal
ou logique

ATTENTION !
Ne pas confondre loprateur daffectation = et loprateur de comparaison ==.

Chapitre 3
Quelques fonctions indispensables
3.1

La fonction printf()

Elle sert afficher lcran la chane de caractre donne en argument, cest--dire entre parenthses.
printf("Bonjour\n") ; affichera Bonjour lcran.
Certains caractres ont un comportement spcial :
\n
\b
\r
\t
\v
\"
\
\?
\!
\\

retour la ligne
nimprime pas la lettre prcdente
nimprime pas tout ce qui est avant
tabulation horizontale
tabulation verticale
"

?
!
\

Mais printf() permet surtout dafficher lcran la valeur dune variable :


main(){
int n=3, m=4 ;
printf("%d",n) ; /* affiche la valeur de n au format d (decimal) */
printf("n=%d",n) ; /* affiche n=3 */
printf("n=%d, m=%d",n,m) ; /* affiche n=3, m=4 */
printf("n=%5d",n) ; /* affiche la valeur de n sur 5 caracteres : n=
*/
}

Le caractre % indique le format dcriture lcran. Ds quun format est rencontr dans la chane
de caractre entre " ", le programme affiche la valeur de largument correspondant.
{

printf("n=%d, m=%d",n,m);

ATTENTION ! le compilateur nempche pas dcrire un char sous le format dun rel affichage de valeurs dlirantes. Et si on crit un char avec un format dcimal, on affiche la valeur du
code ASCII du caractre.
8

%d
%u
%hd
%d
%f
%e
%lf
%le
%c
%s

TAB . 3.1 Tableau des formats utilisables


integer
entier (dcimal)
unsigned entier non sign (positif)
short
entier court
long
entier long
float
rel, notation avec le point dcimal (ex. 123.15)
float
rel, notation exponentielle (ex. 0.12315E+03)
double
rel en double prcision, notation avec le point dcimal
double
rel en double prcision, notation exponentielle
char
caractre
char
chane de caractres

Remarque : une chane de caractres est un tableau de caractres. Elle se dclare de la faon
suivante : char p[10] ;. Mais nous reviendrons sur la notion de tableau plus tard.

3.2

La fonction scanf()

Dans un programme, on peut vouloir quune variable nait pas la mme valeur chaque excution.
La fonction scanf() est faite pour cela. Elle permet de lire la valeur que lutilisateur rentre au clavier
et de la stocker dans la variable donne en argument.
Elle sutilise ainsi :
main(){
int a ;
scanf("%d",&a) ;
}
On retrouve les formats de lecture prciss entre " " utiliss pour printf(). Pour viter tout
risque derreur, on lit et on crit une mme variable avec le mme format.
Le & est indispensable pour le bon fonctionnement de la fonction. Il indique ladresse de
la variable, mais nous reviendrons sur cette notion dadresse quand nous aborderons les
pointeurs.

3.3

La librairie string.h

La librairie string.h fournit un certain nombres de fonctions trs utiles pour manipuler des
chanes de caractres en C. En effet, le C ne sait faire que des affectations et des comparaisons pour 1
seul caractre.
char p,q ;
char chaine1[10], chaine[10] ;
p = A ;
/*
chaine1 = "Bonjour" ;
/*
chaine2 = "Hello" ;
/*
if (p == q) ;
/*
if (chaine1 == chaine2) /*

/* p et q sont des caractres */


/* chaine1 et chaine2 sont des chanes */
/* de caractres */
instruction valide */
instruction NON valide (1) */
instruction NON valide (2) */
instruction valide */
instruction NON valide (3) */
9

Pour faire les affectations (1) et (2), et la comparaison (3), il faudrait donc procder caractre
par caractre. Ces oprations tant longues et sans intrt, on utilise les fonctions dj faites dans
string.h. Pour les oprations daffectation (1) et (2), il faut utiliser la fonction strcpy (string
copy), et pour une comparaison (3), la fonction strcmp (string compare).
#include<stdio.h>
#include<string.h>
main(){
char chaine1[10], chaine2[10] ;
int a ;
strcpy(chaine1,"Bonjour") ; /* met "Bonjour" dans chaine1 */
strcpy(chaine2,"Hello") ;
/* met "Hello" dans chaine2 */
a=strcmp(chaine1,chaine2) ;
/* a reoit la diffrence chaine1 et chaine2 */
/* si chaine1 est class alphabtiquement avant chaine2, alors a<0 */
/* si chaine1 est class aprs chaine2, alors a>0 */
/* si chaine1 = chaine2, alors a = 0 */
/* Ici, "Bonjour" est alphabtiquement avant "Hello", */
/* donc chaine1 est plus petite que chaine2, et a < 0 */
}

10

Chapitre 4
Les instructions de contrle
Ce sont des instructions qui permettent de notamment faire des tests et des boucles dans un programme. Leur rle est donc essentiel. Pour chaque type dinstruction de contrle, on trouvera la fin
de la partie 4 les organigrammes correspondant aux exemples donns.

4.1
4.1.1

Les instructions conditionnelles


Les tests if

Loprateur de test sutilise comme suit :


if (expression) then {instruction ;}
/* Si expression est vraie alors instruction est executee */

if (expression) {
instruction 1 ;
} else {
instruction 2 ;
}
/* Si expression est vraie alors linstruction 1 est executee */
/* Sinon, cest linstruction 2 qui est executee */

if (expression 1) {
instruction 1 ;
} else if (expression 2){
instruction 2 ;
} else if (expression 3){
instruction 3 ;
} else {
instruction 4 ; }
/* Souvent, on imbrique les tests les uns dans les autres */
Remarque : les instructions xcuter peuvent tre des instructions simples {a=b ;} ou un bloc
dinstructions {a=b ; c=d ; ...}.
11

Comme nous lavons dj vu, une expression est vraie si la valeur quelle renvoie est non nulle.
ATTENTION ! les expressions (a=b) et (a==b) sont diffrentes :
if (a==b) vrai si a et b sont gaux
int b=1 ;
if (a=b)
on met la valeur de b dans a. a vaut alors 1. lexpression est donc vraie

4.1.2 Les tables de branchement : switch


Cette instruction sutilise quand un entier ou un caractre prend un nombre fini de valeurs et que
chaque valeur implique une instruction diffrente.
switch(i) {
case 1 : instruction 1 ; /* si i=1 on excute linstruction 1 */
break ; /* et on sort du switch */
case 2 : instruction 2 ; /* si i=2 ... */
break ;
case 10 : instruction 3 ; /* si i=10 ... */
break ;
default : instruction 4 ; /* pour les autres valeurs de i */
break ;
}
ATTENTION ! on peut ne pas mettre les break ; dans les blocs dinstructions. Mais alors on
ne sort pas du switch, et on excute toutes les instructions des case suivants, jusqu rencontrer un
break ;.
Si on reprend lexemple prcdent en enlevant tous les break, alors
? si i=1 on excute les instructions 1, 2, 3 et 4
? si i=2 on excute les instructions 2, 3 et 4
? si i=10 on excute les instructions 3 et 4
? pour toutes les autres valeurs de i, on nexcute que linstruction 4.

4.2

Les boucles

4.2.1 La boucle for


Elle permet dexcuter des instructions plusieurs fois sans avoir crire toutes les itrations. Dans
ce genre de boucle, on doit savoir le nombre ditrations avant d6etre dans la boucle. Elle sutilise
ainsi :
for (i=0 ; i<N ; i++) {
instructions ... ;
}
Dans cette boucle, i est le compteur. Il ne doit pas tre modifi dans les instuctions, sous peine
de sortie de boucle et donc derreur.
` instruction entre parenthses est linitialisation de la boucle.
? La 1ere
eme
? La 2 ` est la condition de sortie de la boucle : tant quelle est vraie, on continue la boucle. ? Et la
`
3eme
est linstruction ditration : sans elle, le compteur reste la valeur initiale et on ne sort jamais
de la boucle.
12

4.2.2 La boucle while


Contrairement la boucke for, on nest pas obligs ici de connatre le nombre ditrations. Il ny
a pas de compteur.
while (expression) {
instructions ... ;
}
Lexpression est value chaque itration. Tant quelle est vraie, les instructions sont excutes.
Si ds le dbut elle est fausse, les instructions ne sont jamais excutes.
ATTENTION ! Si rien ne vient modifier lexpression dans les instructions, on a alors fait une
boucle infinie : while (1) { instructions } en est un exmple.
Exemple dutilisation :
#include <stdio.h>
main(){
float x,R ;
x=1.0 ;
R=1.e5 ;
while (x < R) {
x = x+0.1*x ;
printf("x=%f",x) ;
}
}

4.2.3

La boucle do ... while

la diffrence dune boucle while, les instructions sont excutes au moins une fois : lexpression
est value en fin ditration.
do {
instructions ... ;
} while (expression)
Les risques des faire une boucle infinie sont les mmes que pour une boucle while.

4.2.4 Les instructions break et continue


break fait sortir de la boucle.
continue fait passer la boucle litration suivante.

13

entree dans le if

expression 1 ?

vrai

{
instruction 1;
}

sortie
du if

Les tests

Exemple 1 dorganigramme dun if

entree dans le if

expression 1 ?

vrai

faux

{
instruction 1;
}
sortie
du if

entree du switch

{
instruction 2;
}
i=1 ? vrai
faux

Exemple 2 dorganigramme dun if

{
instructions 1;
}
break ? vrai
faux

entree dans le if

expression 1 ?

vrai

faux

i=2 ? vrai
faux

{
instruction 1;
}

{
instructions 2;
}
break ? vrai
faux

expression 2 ?

vrai

faux

{
instruction 2;
}
i=10 ? vrai
faux

sortie
du if
expression 3 ?

vrai

faux

{
instruction 3;
}

break ? vrai
faux

{
instructions 4;
}

{
instruction 4;
}

Organigramme dun switch

Exemple 3 dorganigramme dun if

entree dans

sortie
du
switch

{
instructions 3;
}

le for

Les boucles

i=0

entree du while

entree du do ... while

instructions ;
}
expression ?

faux

sortie
du
while

vrai
i++

vrai

i<N ?
faux

{
instructions;
}

{
instructions;
}

expression ?

faux

sortie
du
while

vrai

sortie de la boucle

Organigramme
dune boucle for

Organigramme
dune boucle while

Organigramme
dun do ... while

F IG . 4.1 Organigramme rcapitulatif des structures de contrle.

14

Chapitre 5
Les fonctions
Crer une fonction est utile quand vous avez faire le mme type de calcul plusieurs fois dans
le programme, mais avec des valeurs diffrentes. Typiquement, pour le calcul de lintgrale dune
fonction mathmatique f . Comme en mathmatique, une fonction prend un ou plusieurs arguments
entre parenthses et renvoie une valeur.

5.1

Dclaration

On dclare une fonction ainsi :


type nom( type1 arg1 , type2 arg2, ... , typen argn) { */ prototype */
dclaration variables locales ;
instructions ;
return (expression) ;
}
type est le type de la valeur renvoye par la fonction. type1 est le type du 1er argument arg1
... . Les variables locales sont des variables qui ne sont connues qu lintrieur de la fonction.
expression est value lors de linstruction return (expression) ;, cest la valeur que renvoie
la fonction quand elle est appele depuis main().
` ligne de la dclaration est appele le prototype de la fonction.
La 1ere
Exemple de fonction :

float affine( float x ) { /* la fonction affine prend 1 argument rel */


/* et renvoie un argument rel */
int a,b ;
a=3 ;
b=5 ;
return (a*x+b) ; /* valeur renvoyee par la fonction */
}
On nest pas obligs de dclarer des variables locales :

15

float norme( int x, int y ){ /* la fonction norme prend 2 arguments */


/* entiers et renvoie un rsultat rel */
return (sqrt(x*x+y*y)) ; /* sqrt est la fonction racine carree */
}
On peut mettre plusieurs instructions return dans la fonction :
float val_absolue( float x ) {
if (x < 0) {
return (-x) ;
} else {
return (x) ;
}
}
Une fonction peut ne pas prendre dargument, dans ce cas-l, on met la place de la dclaration
des arguments, le mot-cl void :
double pi( void ) { /* pas darguments */
return(3.14159) ;
}
Une fonction peut aussi ne pas renvoyer de valeur, son type sera alors void :
void mess_err( void ) {
printf("Vous n\avez fait aucune erreur\n") ;
return ; /* pas dexpression aprs le return */
}

16

5.2

Appel de la fonction

Une fonction f() peut tre appele depuis le programme principal main() ou bien depuis une
autre fonction g() la condition de rappeler le prototype de f() aprs la dclaration des variables de
main() ou g() :
#include <stdio.h>
main(){
int x,y,r ;
int plus( int x, int y ) ;
x=5 ;
y=235 ;
r=plus(x,y) ; /* appel dune fonction avec arguments */
}
int plus( int x, int y ){
void mess( void ) ;
mess_err() ; /* appel dune fonction sans arguments */
return (x+y)) ;
}
void mess( void ) {
printf("Vous n\avez fait aucune erreur\n") ;
return ;
}
Quand le programme rencontre linstruction return, lappel de la fonction est termin. Toute
instruction situe aprs lui sera ignore.

17

Chapitre 6
Les tableaux
6.1

Dclaration

Comme une variable, on dclare son type, puis son nom. On rajoute le nombre dlments du
tableau entre crochets [ ] :
float tab[5] ; est un tableau de 5 flottants.
int tablo[8] ; est un tableau de 8 entiers.
ATTENTION !
? Les numros des lments dun tableau de n lments vont de 0 a n 1.
? La taille du tableau doit tre une constante (par opposition variable), donc int t1[n] ; o n serait
une variable dj dclare est une mauvaise dclaration. Par contre si on a dfini #define N 100 en
directive, on peut dclarer int t1[N] ; car N est alors une constante.
On peut initialiser un tableau lors de sa dclaration :
float tab[5] = { 1, 2, 3, 4, 5} ; /* init. de tous les lments de tab */
float toto[10] = {2, 4, 6} ; /* equ. toto[0]=2 ; toto[1]=4 ; toto[2]=6 ; */
/* les autres lments de toto sont mis 0. */

6.2

Utilisation

Comme shmatis ci-contre, on accde llment i dun tableau en faisant


suivre le nom du tableau par le numro i entre crochets. Un lment de tableau
sutilise comme une variable quelconque. On peut donc faire rfrence un lment
pour une affectation : x=tab[2], tab[3]=3, ou dans une expression : if (tab[i]
< 0).

float tab[5];
tab[0]
tab[1]
tab[2]
tab[3]
tab[4]

6.3

Cas dun tableau de caractres

Un tableau de caractres est en fait une chane de caractres. Son initialisation peut se faire de
plusieurs faons :
char p1[10]=B,o,n,j,o,u,r ;
char p2[10]="Bonjour" ; /* init. par une chane litterale */
char p3[ ]="Bonjour" ; /* p3 aura alors 8 lments */

18

ATTENTION ! Le compilateur rajoute toujours un caractre null la fin dune chane de caractres. Il faut donc que le tableau ait au moins un lment de plus que la chane litterale.

Tableau 2 dimensions
int tableau[2][3];
.
co 0
l.
co 1
l.
2

Un tableau 2 dimensions est similaire une matrice. Cest en fait


un tableau de tableau 1 dimension, il se dclare donc de la faon suivante :
int tableau[2][3] ; /* tableau de 2 lignes et 3 colonnes */
Comme il est shmatis ci-contre, tableau[i][j] fait rfrence llment de la ligne i et de la colonne i de tableau. Tout comme un lment
dun tableau 1 dimension, tableau[i][j] se manipule comme nimporte
quelle variable.

co
l

6.4

ligne 0
ligne 1

tableau[1][0]

19

Chapitre 7
Les structures
Les structures permettent de rassembler des valeurs de type diffrent. Par exemple, pour une
adresse, on a besoin du numro (int) et du nom de la rue (char).

7.1

Dclaration

On dclare une structure ainsi :


struct adresse {
int numero ;
char rue[50] ;
};
Chaque lment dclar lintrieur de la structure est appel un champ. Le nom donn la
structure est appel tiquette de structure. Ici, on a en fait dclar un type de structure, pas une
variable.
On dclare les variables associes une structure de cette manire :
struct adresse chez_pierre , chez_julie ;
Car si la structure dune adresse est toujours la mme (numro et nom de la rue), chez_pierre et
chez_julie, qui sont des struct adresse diffrentes, nhabitent pas au mme endroit.
On peut initialiser une structure lors de sa dclaration :
struct adresse chez_pierre={ 15 , "rue_Dugommier" } ;

7.2

Manipulation

On accde aux donnes contenues dans les champs dune structure et faisant suivre le nom de la
strucure par un point "." et le nom du champ voulu :
chez_julie.numero=19 ;
strcpy(chez_julie.rue,"avenue Pasteur") ;
Si 2 structures ont le mme type, on peut effectuer :
20

chez_pierre=chez_julie ; /* Pierre a emmnag chez Julie ! */


Mais on ne peut pas comparer 2 structures (avec == ou !=).

7.3

Tableau de structure

On dclare un tableau de structure de la mme faon quun tableau de variables simples : le nombre
dlments est prcis entre crochets.
struct adresse pers[100] ;
Cette dclaration ncessite que la structure adresse ait dj t dclare avant. pers est alors un
tableau dont chaque lment est une structure de type adresse. Et pers[i].rue fait refrence au
`
champ "rue" de la ieme
personne de la structure pers .

7.4

Structure de structure

On peut utiliser une structure comme champ dune autre structure. Dans la ligne des exemples
prcdents, on peut dfinir une structure adresse qui pourra tre utilise dans la structure repertoire.
Elle peut galement tre utilise dans une autre structure.
struct adresse {
int numero ;
char rue[50] ; } ;
struct repertoire {
char nom[20] ;
char prenom[20] ;
struct adresse maison ; } ; /* dclaration dun champ structure */
/* de type adresse appel maison */
struct repertoire monrepertoire[100] ;
strcpy(monrepertoire[0].nom,"Cordier") ;
strcpy(monrepertoire[0].prenom,"Julie") ;
monrepertoire[0].maison.numero = 19 ;
strcpy(monrepertoire[0].maison.rue,"avenue_Pasteur") ;
strcpy(monrepertoire[1].nom,"Durand") ;
strcpy(monrepertoire[1].prenom,"Pierre") ;
monrepertoire[1].maison.numero = 15 ;
strcpy(monrepertoire[1].maison.rue,"rue_Dugommier") ;
Lorsquun tableau fait partie des champs dune structure, on peut accder aux valeurs de ce tableau
par :
char initiale ;
initiale=monrepertoire[1].prenom[0] ; /* initiale de Pierre */

21

Chapitre 8
Les pointeurs
8.1

Stockage des variables en mmoire

Lors de la compiltaion dun programme, lorinateur rserve dans sa mmoire une place pour
chaque variable dclare. Cest cet endroit que la valeur de la variable est stocke. Il associe alors
au nom de la variable ladresse de stockage. Ainsi, pendant le droulement du programme, quand il
rencontre un nom de variable, il va chercher ladresse correspondante la valeur en mmoire.
Pour les dclaractions de variables suivantes :
int a=0xa ; /* a est un entier cod sur 4 octets */
short b=0x0 ; /* b est un entier cod sur 2 octets */
int c=0x14 ; /* c est un entier cod sur 4 octets */
ceci est stock en mmoire :
'

nom

adresse en hexa

valeur code en hexadcimal

(bfbff000)

00

00

(bfbff004)

00

00

(bfbff006)

00

00

00

0a

00

14

&

Ici, on suppose que lespace mmoire servant stocker les donnes commence ladresse (bfbff000).
Chaque case reprsente 1 octet.
Explication du shma :
? a est cod sur 4 octets et son adresse est (bfbff000), donc ladresse de b sera
(bfbff000)+(4)=(bfbff004).
? b est cod sur 8 octets et son adresse est (20004), donc ladresse de c sera
(bfbff004)+(2)=(bfbff006).

8.2

Dfinition et dclaration dun pointeur

Un pointeur est une variable qui a pour valeur ladresse dune autre variable : celle sur laquelle elle
pointe ! Un pointeur est toujours associ un type de variable et un seul. Au moment de la dclaration,
on dtermine le type de variable point par le pointeur, en crivant le type concern, puis le nom du
pointeur avec une * devant :
22

int *pta ; /* la variable pta est un pointeur sur un entier*/


int a ; /* la variable a est un entier*/

8.3

Oprateur dadresse : &

Pour affecter ladresse de la variable a au pointeur pta, on crit linstruction :


pta=&a ;
Cet oprateur signifie donc adresse de.

8.4

Oprateur dindirection : *

Cet oprateur, mis en prfixe dun nom de pointeur signifie valeur de la variable pointe ou, plus
simplement, valeur pointe.
int a=1 ;
int *ptint ; /* declaration dun pointeur sur un entier */
ptint=&a ; /* ptint pointe sur a */
*ptint=12 ; /* la variable pointe par ptint reoit 12*/
printf("a=%d \n",a) ; /* affiche "a=12" */
En fait, manipuler ptint revient manipuler a.

8.5

Mmoire et pointeurs

On reprend lexemple de la partie 8.1 en ajoutant un pointeur dentier :


int a=0xa ;
short b=0x0 ;
int c=0x14 ;
int *ptint ;
ptint=&a ;
Etat de la mmoire :

'

nom

adresse

valeur code en hexadcimal

(bfbff000)

00

00

(bfbff004)

00

00

(bfbff006)

00

ptint

(bfbff010)

bf

&

00

0a

00

00

14

bf

f0

00
%

23

Explication : Les cases mmoire des variables a, b et c contiennent leur valeur. Celles de la variable
ptint contiennent ladresse de la valeur pointe. En effet, la valeur stocke est (bfbff000), ce qui
est bien ladresse de a.

8.6

Exemple

Voici un petit exemple dillustration :


#include <stdio.h>
main(){
float *px ;
float x=3.5 ;
px=&x ;
printf
printf
printf
printf
return
}

("adresse de x : 0x%lx \n",&x) ;


("valeur de px : 0x%lx \n",px) ;
("valeur de x : %3.1f \n",x) ;
("valeur pointee par px : %3.1f \n",*px) ;
0;

Le programme prcdent affichera ceci lcran :


$
$
$
$

adresse de x : 0xbfbffa3c
valeur de px : 0xbfbffa3c
valeur de x : 3.5
valeur pointee par px : 3.5

24

Chapitre 9
Pointeurs et fonctions
Un variable globale est une variable connue dans toutes les fonctions dun programme (main
comme les autres).
Une variable locale nest connue qu lintrieur dune fonction (main ou une autre).
Les variables locales dune fonction sont regroupes dans une partie de la mmoire, et celles dune
autre fonction, dans un autre endroit. Ainsi, il peut exister un float a ; dans une fonction et un int
a dans une autre sans quil y ait de conflit.
Une conscquence de cette proprit est que la fonction suivante ne marchera pas :
void permute (int a , int b){
int buf ;
buf = a ;
a = b;
b = buf ;
return ;
}
car lors de lappel de cette fonction depuis main, les valeurs des arguments vont tre copis dans
les variables de permute et ce sont ces variables locales qui vont tre modifies, pas celles de main.
Ainsi, dans main, un appel du type permute(i,j) laissera i et j inchangs. On dit que le C passe ses
arguments par valeur.
Pour que permute fonctionne, il faut que ses arguments soient les adresses des variables a et b et
utiliser des pointeurs.
void permute (int *a , int *b){
int buf ;
buf = *a ;
*a = *b ;
*b = buf ;
return ;
}
Lors de lappel de la fonction, les pointeurs locaux vont recevoir ladresse des variables a et b.
Donc travailler sur ces pointeurs revient travailler sur les variables a et b de la fonction main.
Lappel dune telle fonction se fait ainsi : permute (&a ,&b)
De faon gnrale, on utilise des pointeurs avec les fonctions quand on veut quune fonction
modifie des variables du programme principal.

25

Chapitre 10
Pointeurs et tableaux
10.1

Pointeurs et tableaux 1 dimension

Vous avez dja manipul des pointeurs quand vous avez manipul les tableaux. En fait, le nom seul
du tableau est une constante qui contient ladresse du premier lment du tableau comme le shmatise
la figure suivante :
tab

tab[0]
tab[1]
tab[2]
.
.
.
tab[9]

Ainsi, tab est gal &tab[0] et donc *tab est gal tab[0].
Llment tab[i] est quivalent *(tab+i). On a donc les correspondances suivantes :
tab
tab+1
tab+2
.
.
.

tab[0]
tab[1]
tab[2]
.
.
.

tab+9

tab[9]

26

Au niveau de la mmoire, pour les dclarations suivantes,


int
int
int
pta
ptb

tab[3] = { 0xa , 0x3 , 0xd } ;


*pta ;
*ptb ;
= tab ;
= pta+2 ;

voici ltat de la mmoire :


'

nom

adresse

valeur code en hexadcimal

tab

(bfbff000)
(bfbff004)
(bfbff008)

00
00
00

00
00
00

00
00
00

0a
03
0d

pta

(bfbff010)

bf

bf

f0

00

ptb

(bfbff014)

bf

bf

f0

08

&

Explications : Lordinateur a rserv en mmoire 3 fois 4 octets pour le tableau de 3 entiers


tab. Le pointeur pta contient ladresse du 1er lment de tab : (bfbff000). Lopration ptb=pta+2
najoute pas 2 la valeur de pta, mais ajoute 2 fois le nombre doctets correspondant un int,
puisque ptb est un pointeur dentiers. Donc ptb vaut (bfbff000)+(2*4)=(bfbff008). Et ptb
contient alors ladresse de tab[2].
Ainsi, si on dclare
int tab[10] ;
int *pt ;
pt = tab ;
on aura les quivalences suivantes :
*tab
tab[0]
*pt

*(tab+1) tab[1] *(pt+1)


et de faon plus gnrale, pour i de 0 9 :
*(tab+i) tab[i] *(pt+i)

27

pt[0]
pt[1]
pt[i]

10.2

Pointeurs et tableaux plusieurs dimensions

Un tableau plusieurs dimensions est un tableau dont les lments sont eux-mmes des tableaux.
Ainsi, le tableau dfini par int tab[4][5] ; contient 4 tableaux de 5 entiers chacuns. tab donne
`
ladresse du 1er sous-tableau, tab+1 celle du 2eme
sous-tableau et ainsi de suite :
tab
tab[0][0]
tab[0][1]
tab[0][2]
tab[0][3]
tab[0][4]
tab+1

tab[1][0]
tab[1][1]
tab[1][2]
tab[1][3]
tab[1][4]

tab+2

tab[2][0]
tab[2][1]
tab[2][2]
tab[2][3]
tab[2][4]

tab+3

tab[3][0]
tab[3][1]
tab[3][2]
tab[3][3]
tab[3][4]
Ici, lopration tab+2 najoute pas 2 la valeur de tab mais ajoute 2 fois le nombre doctets
correspondant un tableau de 5 entiers, savoir 5*4 = 20 octets.

10.3

Tableaux de pointeurs

La dclaration dun tableau de pointeurs se fait comme pour un tableau de variables quelconques :
le type puis le nom du tableau avec
le nombre dlments entre crochets derrire le nom
et une * devant.
int *pttab[4] ; /* pttab est un tableau de 4 pointeurs dentiers */

1er exemple dutilisation dun tableau de pointeurs


int tab[4] ; /* tab est un tableau de 4 entiers */
int *pttab[4] = { tab , tab+1 , tab+2 , tab+3 } ;
Les valeurs du tableau pttab sont des adresses de donnes. On dit alors que pttab est un pointeur
de pointeurs car (pttab) pointe sur ladresse de son 1er lment, qui est lui-mme une adresse. Voici
les relations quil y a entre pttab et tab :
28

pointeur de
pointeur
pttab

pttab+1

pttab+i

pointeur
pttab[0]
*pttab
tab

*(pttab[0])
**pttab
*tab
tab[0]

pttab[1]
*(pttab+1)
tab+1

*(pttab[1])
**(pttab+1)
*(tab+1)
tab[1]

pttab[i]
*(pttab+i)
tab+2

*(pttab[i])
**(pttab+i)
*(tab+i)
tab[i]

Note : Les expressions se situant dans une mme case sont quivalentes.
Au niveau de la mmoire, les lments dune colonne contiennent les adresses des lments de la
colonne qui est juste sa droite.
Voici ltat de la mmoire pour un tel exemple :
'

nom

adresse

valeur code en hexadcimal

tab

(bfbff000)
(bfbff004)
(bfbff008)
(bfbff00c)

00
00
00
00

00
00
00
00

00
00
00
00

0a
03
0d
6c

pttab

(bfbff010)
(bfbff014)
(bfbff018)
(bfbff01c)

bf
bf
bf
bf

bf
bf
bf
bf

f0
f0
f0
f0

00
04
08
0c

&

29

`
2eme
exemple

int
int
int
int

l1[4] = { 1 , 2 , 3 , 4 } ;
l2[3] = { 5 , 6 , 7 } ;
l3[1] = { 8 } ;
*pt[3] = { l1 , l2 , l3 } ;

Les lments de tab sont les adresses de tableaux ne comportant pas le mme nombre dlments.
pt est en fait un tableau dont les 3 lignes nont pas la mme longueur.
pt

pt[0]

l1

l1[0]
l1[1]
l1[2]
l1[3]

pt[0][0]
pt[0][1]
pt[0][2]
pt[0][3]

pt+1

pt[1]

l2

l2[0]
l2[1]
l2[3]

pt[1][0]
pt[1][1]
pt[1][2]

pt+2

pt[2]

l3

l3[0]

pt[2][0]

tat de la mmoire :

'

nom

adresse

l1

(bfbff000)
(bfbff004)
(bfbff008)
(bfbff00c)

00
00
00
00

00
00
00
00

00
00
00
00

01
02
03
04

l2

(bfbff010)
(bfbff014)
(bfbff018)

00
00
00

00
00
00

00
00
00

05
06
07

l3

(bfbff01c)

00

00

00

08

pt

(bfbff020)
(bfbff024)
(bfbff028)

bf
bf
bf

bf
bf
bf

f0
f0
f0

00
10
1c

&

valeur code en hexadcimal

30

Chapitre 11
Allocation dynamique de mmoire
Jusqu maintenant, lors de la dclaration dun tableau, il fallait prciser les dimensions, soit de
faon explicite :
int tab[3][2] ;
soit de faon implicite :
int tab[][] = { {0 , 1 } , {2 , 3 } , {4 , 5 } } ;
Dans les 2 cas, on a dclar un tableau de 3 fois 2 entiers.
Mais si lon veut que le tableau change de taille dune excution une autre, cela nous oblige
modifier le programme et le recompiler chaque fois, ou bien dclarer un tableau de 1000 fois
1000 entiers et nutiliser que les n premires cases mais ce serait du gchis.
Pour viter cela, on fait appel lallocation dynamique de mmoire : au lieu de rserver de la place
lors de la compilation, on la rserve pendant lexcution du programme, de faon interative.

11.1

La fonction malloc()

Pour lutiliser il faut inclure la bibliothque <stdlib.h> en dbut de programme.


malloc( N ) renvoie ladresse dun bloc de mmoire de N octets libres (ou la valeur 0 sil ny a pas
assez de mmoire).
Exemple :
int *p ;
p = malloc(800) ; /* fournit ladresse dun bloc de 800 octets libres */
/* et laffecte au pointeur p */

11.2

Loprateur sizeof()

Dune machine une autre, la taille rserve pour un int, un float,... change. Si nous voulons
rserver de la mmoire pour des donnes dun type dont la grandeur varie dune machine lautre,
nous avons besoin de la taille effective dune donne de ce type.
Loprateur sizeof nous fournit ce renseignement.
sizeof nom_variable fournit la taille de la variable nom_variable

31

sizeof nom_constante ournit la taille de la constante nom_constante


sizeof (type) fournit la taille pour un objet du type type
Ainsi, les instructions suivantes :
int a[10] ;
char b[5][10] ;
printf("taille
printf("taille
printf("taille
printf("taille
printf("taille
printf("taille

de a : %d\n",sizeof a) ;
de b : %d\n",sizeof b) ;
de 4.25 : %d\n",sizeof 4.25) ;
de Bonjour ! : %d\n",sizeof "Bonjour !") ;
dun float : %d\n",sizeof(float)) ;
dun double : %d\n",sizeof(double)) ;

produiront lexcution sur un PC :


taille
taille
taille
taille
taille
taille

11.3

de a : 40
de b : 40
de 4.25 : 8
de Bonjour ! : 9
dun float : 4
dun double : 8

Allocation dynamique pour un tableau 1 dimension

On veut rserver de la place pour un tableau de n entiers, o n est lu au clavier :


int n ;
int *tab ;
printf("taille du tableau :\n") ;
scanf("%d", &n) ;
tab = (int *)malloc(n*sizeof(int)) ;
Les (int *) devant le malloc sappelle un cast. Un cast est un oprateur qui convertit ce qui suit
selon le type prcis entre parenthses.
Exemple :
int n1 , n2 ;
float x = 4.5 ;
n1 = ((int) x)*10 ;
n2 = (int)(x*10) ;
Pour n1, on convertit dabord x en entier et on multiplie le rsultat par 10 : n1 = 40
Pour n2, on convertit en entier x*10 : n2 = 45
La fonction malloc renvoie juste ladresse de dbut dun bloc de n fois sizeof(int). Elle renverra donc la mme chose pour un tableau de 400 char que pour 100 int : ladresse dun bloc de 400
octets. Cest pourquoi le cast est ncessaire : pour prciser le type de donnes sur leqsuelles tab va
pointer.
32

`
tab contient alors ladresse de dbut dun bloc de n entiers et on accde la ieme
valeur du tableau
par tab[i].
Jusqu maintenant, on a vu des pointeurs qui contenaient ladresse dune variable en mmoire.
Ici, on a lexemple dun pointeur qui contient ladresse dun bloc contenant des donnes. Celles-ci ne
sont accessibles que via un pointeur.
tat de la mmoire avant lallocation :

'

nom

adresse

valeur code en hexadcimal

(bfbff000)

00

00

00

04

tab

(bfbff004)

00

00

00

00

lutilisateur a entre 4 comme valeur de n

&

tat de la mmoire aprs lallocation :


'

nom

adresse

valeur code en hexadcimal

(bfbff000)

00

00

00

04

tab

(bfbff004)

80

00

00

00

(80000000)
(80000004)
(80000008)
(8000000c)

00
00
00
00

00
00
00
00

00
00
00
00

00
00
00
00

&

(tab[0])
(tab[1])
(tab[2])
(tab[3])
%

33

11.4

Allocation dynamique pour un tableau plusieurs dimensions

On veut rserver de la place pour un tableau de n fois m entiers, o n et m sont lus au clavier : On a
vu que pour manipuler des tableaux plusieurs dimensions, il fallait utiliser des tableaux de pointeurs
(i.e. des pointeurs de pointeurs).
int i,j,n,m ;
float **tab ; /* (1) */
scanf("%d%d",n,m) ;
tab = (float **)malloc(n*sizeof(float *)) ; /* (2) */
for (i=0 ; i<n ; i++){
tab[i] = (float *)malloc(m*sizeof(float)) ; /* (3) */
}
for (i=0 ; i<n ; i++){
for (j=0 ; j<m ; j++){
tab[i][j] = 10*i+j ; /* (4) */
}
}
Explications :
(1) Un tableau de pointeurs tant un pointeur de pointeurs, on peut dclarer au choix un tableau
de pointeurs : int *tab[3] ou un pointeur de pointeur : **tab. Dans le cas de lallocation
dynamique de mmoire, comme on ne connait pas la taille du tableau dont on aura besoin, on
dclare donc un pointeur de pointeur.
(2) tab tant en fait un tableau de pointeurs de flottants, on rserve un bloc pouvant contenir n
pointeurs de float. tab contient alors ladresse de ce bloc.
(3) Les tab[i] sont des sous-tableaux de tab. On rserve alors pour chacun deux de la place pour
m flottants. Au total, on a bien rserv de la place pour n fois m flottants.
(4) On manipule les lments de tab comme ceux dun tableau "normal".
tat de la mmoire aprs ltape (2) :
'

nom

adresse

valeur code en hexadcimal

(bfbff000)

00

00

00

03

(bfbff000)

00

00

00

02

tab

(bfbff004)

80

00

00

00

(80000000)
(80000004)
(80000008)

00
00
00

00
00
00

00
00
00

00
00
00

&

tat de la mmoire la fin du programme :

34

'

nom

adresse

valeur code en hexadcimal

(bfbff000)

00

00

00

03

(bfbff000)

00

00

00

02

tab

(bfbff004)

80

00

00

00

(80000000)
(80000004)
(80000008)

80
80
80

00
00
00

01
0a
0d

00
00
00

(adresse du ss-tableau tab[0])


(adresse du ss-tableau tab[1])
(adresse du ss-tableau tab[2])

(80000100)
(80000104)

00
00

00
00

00
00

00
01

(valeur de tab[0][0])
(valeur de tab[0][1])

(80000a00)
(80000a04)

00
00

00
00

00
00

0a
0b

(valeur de tab[1][0])
(valeur de tab[1][1])

(80000d00)
(80000d04)

00
00

00
00

00
00

14
15

(valeur de tab[2][0])
(valeur de tab[2][1])

&

Note : Les diffrentes allocations de mmoire ne se font pas en mme temps. Cest pour cela que
les diffrents blocs mmoire ne sont pas contigs.

35

Vous aimerez peut-être aussi