Rado A. Rakotonarivo

ATER (Attaché Temporaire d'Enseignement et de Recherche), IRIF, Université de Paris, France

raktn at irif.fr

« Retour

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 :

Voyons ce qui se passe en mémoire quand on déclare et initialise la variable suivante.

int x = 10;
   x
--------
|  10  |
--------
   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.

int x = 10;
int *px = &x;
   x                px
--------         --------
|  10  |         |  80  | (80 est l'adresse de x)
--------         --------
   80               102

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

int x = 10;
int *px = &x;
printf("L'adresse de x est : %p\n", &x);
printf("Le contenu de px est : %p\n", px);

Ce bout de code produit l’affichage suivant :

L'adresse de x est : 0x7ffeeedda838
Le contenu de px est : 0x7ffeeedda838  

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 :

$ gcc -Wall -o run demo_portee.c
demo_portee.c:12:5: error: use of undeclared identifier 'b'
  ++b;
    ^
demo_portee.c:13:34: error: use of undeclared identifier 'b'
  printf("b (main) vaut : %d\n", b)
                                 ^
2 errors generated.
$

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 :

$ gcc -Wall -o run demo_portee.c
$ ./run
a vaut : 1
b (if) vaut : 2
$

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:

En mémoire cela ressemble à ça :

           x   |   0   | 102                          x |   1   | 102
               ---------                                ---------
incrementer(0) |       |                 incrementer(0) |       |
               ---------                                ---------
        main() |       |                         main() |       |
               ---------                                ---------
ma_var_globale |   0   | 80              ma_var_globale |   0   | 80   
               =========                                =========

        main() |       |
               ---------        ----> Le programme affiche : Ma variable globale vaut : 0
ma_var_globale |   0   | 80
               =========              

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

void incrementer (int * x) {
  ++(*x); // incrémente le contenu de la variable dont l'adresse est contenu dans x
}

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);
}