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 2 3 4 5 6 7 8 9 Les bases Variables et constantes Quelques fonctions indispensables Les instructions de contrle Les fonctions Les tableaux Les structures Les pointeurs Pointeurs et fonctions 3 5 8 11 15 18 20 22 25 26 31

10 Pointeurs et tableaux 11 Allocation dynamique de mmoire

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 nissent par */, ils peuvent stendre sur plusieurs lignes.

1.1.1

Les directives de prcompilation


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

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

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 lafchage 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 ; ... } Exemple de programme simple : #include <stdio.h> /* Mon 1er programme en C */ main(){ printf("Hello world\n") ; }

instruction_1 ;

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 chier 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 = 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 dnies 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 signication 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 prxe 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 ; Il ny a pas de type complexe.

signication 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

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 spciques, 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 dnis 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 nir, 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 afcher lcran la chane de caractre donne en argument, cest--dire entre parenthses. printf("Bonjour\n") ; afchera 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 dafcher 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 afche 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 afchage de valeurs dlirantes. Et si on crit un char avec un format dcimal, on afche 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 n 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 ni 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 modi dans les instuctions, sous peine de sortie de boucle et donc derreur. ` La 1ere instruction entre parenthses est linitialisation de la boucle. eme est la condition de sortie de la boucle : tant quelle est vraie, on continue la boucle. Et la La 2 ` ` 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 modier lexpression dans les instructions, on a alors fait une boucle innie : 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 n ditration. do { instructions ... ; } while (expression) Les risques des faire une boucle innie 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 { instruction 1; } sortie du if

expression 1 ?

vrai

Exemple 1 dorganigramme dun if

Les tests

entree dans le if { instruction 1; } sortie du if { instruction 2; } i=1 ? vrai faux { instructions 1; } break ? vrai faux entree dans le if { instruction 1; } i=2 ? vrai faux { instructions 2; } break ? vrai faux expression 2 ? faux vrai { instruction 2; } sortie du if expression 3 ? faux vrai { instruction 3; } i=10 ? vrai faux sortie du switch entree du switch

expression 1 ? faux

vrai

Exemple 2 dorganigramme dun if

expression 1 ? faux

vrai

{ instructions 3; } break ? vrai faux

{ instruction 4; }

{ instructions 4; }

Exemple 3 dorganigramme dun if

Organigramme dun switch

entree dans i=0

le for

Les boucles
entree du while entree du do ... while

{ instructions ; }

expression ? vrai i++

faux

sortie du while

{ instructions; }

vrai

i<N ? faux

{ instructions; }

expression ? vrai

faux

sortie du while

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().
` La 1ere ligne de la dclaration est appele le prototype de la fonction. 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 ottants. 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 dni #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
float tab[5];
tab[0] tab[1] tab[2] tab[3] tab[4]

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).

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 n dune chane de caractres. Il faut donc que le tableau ait au moins un lment de plus que la chane litterale.

6.4

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.

ligne 0 ligne 1

tableau[1][0]

19

co l

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 dnir 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 a b c
&

adresse en hexa (bfbff000) (bfbff004) (bfbff006)

valeur code en hexadcimal 00 00 00 00 00 00 00 14


%

00

0a

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

Dnition 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 signie donc adresse de.

8.4

Oprateur dindirection : *

Cet oprateur, mis en prxe dun nom de pointeur signie 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 a b c ptint
&

adresse (bfbff000) (bfbff004) (bfbff006) (bfbff010)

valeur code en hexadcimal 00 00 00 bf 00 00 00 bf 00 f0 14 00


%

00

0a

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 afchera 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 conit. 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 modies, 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 modie 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 gure 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+9 tab[0] tab[1] tab[2] . . . 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 tab

adresse (bfbff000) (bfbff004) (bfbff008) (bfbff010) (bfbff014)

valeur code en hexadcimal 00 00 00 bf bf 00 00 00 bf bf 00 00 00 f0 f0 0a 03 0d 00 08


%

pta ptb
&

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) pt[0] pt[1] pt[i]

27

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 dni 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][0] tab[2][1] tab[2][2] tab[2][3] tab[2][4]

tab+2

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.

tab+3

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

pointeur pttab[0] *pttab tab

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

pttab+1

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

pttab+i

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

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 tab

adresse (bfbff000) (bfbff004) (bfbff008) (bfbff00c) (bfbff010) (bfbff014) (bfbff018) (bfbff01c)

valeur code en hexadcimal 00 00 00 00 bf bf bf bf 00 00 00 00 bf bf bf bf 00 00 00 00 f0 f0 f0 f0 0a 03 0d 6c 00 04 08 0c


%

pttab

&

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][0] pt[1][1] pt[1][2] pt[2][0]
$

pt+1

pt[1]

l2

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

pt+2 tat de la mmoire :

pt[2]

l3

'

nom l1

adresse (bfbff000) (bfbff004) (bfbff008) (bfbff00c) (bfbff010) (bfbff014) (bfbff018) (bfbff01c) (bfbff020) (bfbff024) (bfbff028)

valeur code en hexadcimal 00 00 00 00 00 00 00 00 bf bf bf 00 00 00 00 00 00 00 00 bf bf bf 00 00 00 00 00 00 00 00 f0 f0 f0 01 02 03 04 05 06 07 08 00 10 1c

l2

l3 pt
&

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 modier 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 de a : 40 de b : 40 de 4.25 : 8 de Bonjour ! : 9 dun float : 4 dun double : 8

11.3

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 n tab
&

adresse (bfbff000) (bfbff004)

valeur code en hexadcimal 00 00 00 00 00 00 04 00 lutilisateur a entre 4 comme valeur de n

% ' $

tat de la mmoire aprs lallocation : nom n tab adresse (bfbff000) (bfbff004) valeur code en hexadcimal 00 80 00 00 00 00 04 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 ottants, 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 ottants. Au total, on a bien rserv de la place pour n fois m ottants. (4) On manipule les lments de tab comme ceux dun tableau "normal". tat de la mmoire aprs ltape (2) :
' $

nom n m tab

adresse (bfbff000) (bfbff000) (bfbff004)

valeur code en hexadcimal 00 00 80 00 00 00 00 00 00 03 02 00

(80000000) (80000004) (80000008)


&

00 00 00

00 00 00

00 00 00

00 00 00
%

tat de la mmoire la n du programme :

34

'

nom n m tab

adresse (bfbff000) (bfbff000) (bfbff004)

valeur code en hexadcimal 00 00 80 00 00 00 00 00 00 03 02 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