Vous trouverez les énoncés des TD sur le moodle du cours ici.
Variables et Pointeurs
Rappel : une variable
est une zone mémoire caractérisée par :
- un nom (permettant de l'identifier dans notre programme)
- un type (pour spécifier le nombre d'octet nécessaire pour encoder notre variable)
- une valeur (la donnée contenue à l'intérieur de notre variable)
- une adresse (l'endroit de la mémoire où est enregistrée notre variable) [déterminée à l'execution]
Voyons ce qui se passe en mémoire quand on déclare et initialise la variable suivante.
- On déclare une variable qui sera identifié par le nom
x
. x
est de type entier (int
). Dans la mémoire, les entiers sont représentés sur 4 octets. La zone mémoire réservée pourx
sera donc de 4 octets.- On initialise
x
à 10, iex
a pour valeur initiale 10. - Puis imaginons que
x
soit stockée à l’adresse 80 de la mémoire, ie son adresse est 80.
Pointeur ? un pointeur
est une variable dont le contenu est l’adresse d’une autre variable (pour l’instant 😉). Puisqu’un pointeur
est une variable il a donc toutes les caractéristiques d’une variable : nom, type, valeur et adresse.
- On déclare une variable nommée
px
. px
est de typeint *
. Cela indique que la variable dont l’adresse est contenue danspx
a pour typeint
.- On initiale
px
à l’adresse dex
notée&x
. On dit quepx
pointe surx
et quex
est pointé parpx
. - Puis imaginons qu’à l’execution
px
soit stockée à l’adresse 102 dans la mémoire.
Le type
du pointeur determine la taille de la tête de lecture. Quand on écrit *px
cela signifie : va à l'adresse contenue dans px
et lit 4 octets
à partir de cette adresse (car px
est de type int *
).
On affiche le contenu d’un pointeur (une adresse) avec le format "%p"
.
Ce bout de code produit l’affichage suivant :
Mais à quoi ça sert ? Un pointeur permet d’accéder (lire et écrire) à la valeur de la variable dont l’adresse est contenue dans le pointeur. Si px
est de type int *
, alors *px
est de type int
.
Qu’affichera le programme suivant ?
1
2
3
4
5
6
7
8
9
#include <stdio.h>
int main () {
int x = 10;
int *px = &x;
(*px) = (*px) + 1;
printf("x vaut : %d\n", x);
return 0;
}
Le programme affichera x vaut : 11
. À la ligne 6, l’instruction (*px) = (*px) + 1;
incrémente la valeur de la variable dont l’adresse est contenue dans px
ie la valeur de x
.
On verra par la suite toutes les possibilités que l’on peut avoir avec les pointeurs. Cependant il faut faire attention à leur utilisation car on a vu qu’avec les pointeurs on a directement accès à la mémoire.
Fonctions
Le petit rappel sur les fonctions
est disponible ici
Portée des variables
On rappelle qu’un bloc d’instructions est délimité par des accolades :'{'
et '}'
. En règle générale, la portée d'une variable
est limitée dans le bloc dans lequel elle a été déclarée. C’est-à-dire qu’elle n’est accessible en lecture et en écriture qu’à l’intérieur du bloc dans lequel la variable a été déclarée.
Le code demo_portee.c
suivant produit une erreur :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <stdio.h>
int main(int argc, char const *argv[]) {
int a = 0;
++a;
if (a == 1) {
int b = 2;
printf("a vaut : %d\n", a);
printf("b (if) vaut : %d\n", b);
}
++b;
printf("b (main) vaut : %d\n", b)
return 0;
}
En compilant on tombe sur l’erreur suivante :
Effectivement la portée de la variable b
, déclarée dans le bloc du if
est limitée à l’intérieur de celui-ci : on dit que c’est une variable locale au bloc du if
. En commentant les lignes 12
et 13
, nous corrigeons le programme et la compilation ne renvoie plus d’erreur.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <stdio.h>
int main() {
int a = 0;
++a;
if (a == 1) {
int b = 2;
printf("a vaut : %d\n", a);
printf("b (if) vaut : %d\n", b);
}
// ++b;
// printf("b (main) vaut : %d\n", b)
return 0;
}
L’execution du programme affiche le résultat suivant :
- La variable
a
, déclarée dans le bloc de la fonctionmain
est accessible partout au sein de la fonctionmain
. - La portée de la variable
b
est limitée dans le bloc duif
.
Toute variable déclarée à l’intérieur d’un bloc est appelée variable locale au bloc. Par contre, si on veut qu’une variable soit accessible partout dans notre programme, elle doit être déclarée en dehors de tout bloc : on parle alors de variable globale.
Pour le moment, quand on voudra déclarer des variables globales, on le fera avant la définition de la fonction main
.
1
2
3
4
5
6
7
8
9
#include <stdio.h>
int ma_var_globale = 0;
int main () {
++ma_var_globale;
printf("Ma variable globale vaut : %d\n", ma_var_globale);
return 0;
}
On sait qu’une variable globale est accessible partout dans notre programme. Écrivons une fonction void incrementer()
et déplaçons la ligne 6
dans cette fonction, et finalement appelons la fonction incrementer()
dans le main.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <stdio.h>
int ma_var_globale = 0;
void incrementer ();
int main () {
incrementer();
printf("Ma variable globale vaut : %d\n", ma_var_globale);
return 0;
}
void incrementer () {
++ma_var_globale;
}
L’execution de notre programme affiche Ma variable globale vaut : 1
. On a alors changé la valeur de ma_var_globale
à l’intérieur d’une fonction. Bien qu’elle fonctionne déjà très bien, on voudrait maintenant que la fonction incrementer()
puisse incrémenter autre chose que ma_var_globale
. On voudrait la modifier de manière à ce qu’elle prenne en argument la variable dont on veut incrémenter la valeur. On a alors le code suivant :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <stdio.h>
int ma_var_globale = 0;
void incrementer ();
int main () {
incrementer(ma_var_globale);
printf("Ma variable globale vaut : %d\n", ma_var_globale);
return 0;
}
void incrementer (int x) {
++x;
}
Que va afficher le programme ? Et bien le programme affiche Ma variable globale vaut : 0
. En effet, en passant ma_var_globale
en paramètre de la fonction incrementer()
, le language C
procède à ce que l’on appelle un passage par valeur ou passage par copie. C’est-à-dire:
- La valeur que l'on modifie dans la fonction
incrementer()
n'est pas le contenu dema_var_globale
mais le contenu dex
. - La variable
x
est une variable dont la valeur est une copie de la valeur dema_var_globale
mais à un endroit différent de la mémoire.
En mémoire cela ressemble à ça :
La solution pour réellement modifier la valeur passée en paramètre c’est de demander à la fonction d’aller à l’adresse mémoire où se trouve notre variable et d’y modifier sa valeur. Cela tombe bien on a vu plus haut que cela était possible avec les pointeurs. On ne va plus passer une valeur en paramètre mais plutôt une adresse. Voici donc la nouvelle version de la fonction incrementer()
Voici le programme entier :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <stdio.h>
int ma_var_globale = 0;
void incrementer (int *);
int main () {
incrementer(&ma_var_globale); // On passe ici en paramètre l'adresse de ma_var_globale
printf("Ma variable globale vaut : %d\n", ma_var_globale);
return 0;
}
void incrementer (int * x) {
++(*x);
}
On appelle se que l’on vient de faire un passage par adresse. L’avantage c’est qu’on peut directement accéder à n’importe quelle variable, globale ou non, en passant son adresse en paramètre d’une fonction.
À vous de jouer. Qu’affiche le programme suivant ?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include <stdio.h>
void incrementer (int *);
int main () {
int a = 0;
int *x = NULL;
incrementer(&a);
if (a == 1) {
int b = 2;
x = &b;
printf("a : %d\n", a);
incrementer(x);
}
printf("b : %d\n", *x);
return 0;
}
void incrementer (int * x) {
++(*x);
}