De la mémoire pour les programmes
Pointeurs
Les pointeurs et les références sont des outils particulièrement intéressants. Le langage C ne connaît pas les références, mais il peut travailler avec les pointeurs en suivant les règles applicables aux références. Des langages plus récents, comme Java, ont supprimé les pointeurs de leur vocabulaire. Non pas parce qu’ils pourraient avoir mauvaise réputation auprès des programmeurs, mais parce qu’ils agissent à un niveau plus bas que les références, ce qui perturbe l’usage d’outils de haut niveau tels que le ramasse-miettes (garbage collector).
Pointeurs et références sont des variables qui permettent d’atteindre d’autres variables. Pour parvenir à ce résultat, le pointeur (ou la référence) utilise l’adresse de la variable cible, c’est-à-dire le numéro de la case mémoire où est rangée la valeur de cette variable. Comme la mémoire est comptée en octets, et qu’une variable peut répartir la représentation de sa valeur sur plusieurs octets, pointeurs et références sont des variables définies pour travailler avec un type donné, dans le but de limiter les erreurs d’adressage.
1. Pointeurs sur des variables
Commençons par étudier la représentation d’un fragment de la mémoire de l’ordinateur. Ce fragment contient une variable x de type char, préalablement initialisée à la valeur 3. Le plus souvent, les adresses s’écrivent en hexadécimal pour mieux les distinguer des valeurs stockées en mémoire, mais aussi car les adresses 16 ou 32 bits s’écrivent facilement dans cette base.
![images/02ppn05.png](https://www.eni-training.com/download/c73a35ed-b3ca-4eb5-b278-9a245041bea1/images/02ppn05.png?id=AAEAAAD%2f%2f%2f%2f%2fAQAAAAAAAAAMAgAAAE1FbmkuRWRpdGlvbnMuTWVkaWFwbHVzLCBWZXJzaW9uPTEuMC4wLjAsIEN1bHR1cmU9bmV1dHJhbCwgUHVibGljS2V5VG9rZW49bnVsbAUBAAAAJ0VuaS5FZGl0aW9ucy5NZWRpYXBsdXMuQ29tbW9uLldhdGVybWFyawIAAAAHcGlzVGV4dAlwaWR0ZURhdGUBAA0CAAAABgMAAAAwdGVzdCB2Ml84IC0gZmZhNjQzOWQtZDZjYi00NzYxLWJhYTMtZWZkZDNiZDcwMWM25rSq0F7p2IgL)
Pour affecter la valeur 10 à la variable x, par exemple, nous pouvons utiliser l’extrait de code suivant :
char x=3;
x=10;
Si nous pouvions obtenir l’adresse de la variable x, 0x1003 dans notre cas, nous pourrions modifier cette variable sans utiliser directement x. Pour cela, nous allons définir un pointeur de type char, noté char*. Cette variable spéciale, p, recevra l’adresse de la variable x, déterminée à l’aide d’une syntaxe spéciale....
Références
Bien qu’accomplissant le même rôle que les pointeurs, les références offrent une syntaxe plus simple et en même temps limitent les risques d’accès erroné à la mémoire.
Une référence est toujours associée à une variable, alors qu’un pointeur peut être modifié via l’arithmétique des pointeurs.
La syntaxe de définition d’un type référence utilise le préfixe &, en remplacement de l’étoile. Cependant, il ne faut pas confondre ce préfixe avec l’opérateur & qui extrait l’adresse d’une variable ou d’une fonction.
char c; // un caractère
char*p; // un pointeur de char
p=&c; // p désigne c, et &c représente l'adresse de c
char & refc=c; // refc est une référence de char, refc désigne c
Dans cet extrait de code, nous avons défini une référence de char, refc, désignant la variable c. Cette référence est devenue un alias de la variable c, aussi, toute modification amenée par refc impactera en réalité c, même en dehors de sa portée :
refc++; // incrémente en fait c
Comme dans le cas des pointeurs, les références n’ont...
Constantes
1. Constantes symboliques
Le préprocesseur, utilitaire de prétraitement textuel, remplit trois missions importantes :
-
Il inclut les fichiers désignés par la directive #include.
-
Il évalue la présence de macros par la directive #ifdef.
-
Il évalue les macros définies par la directive #define.
Ce préprocesseur travaille en amont du compilateur. Depuis que les nouveaux langages de programmation ont abandonné son usage (Java par exemple), il vaut mieux limiter le nombre de macros définies avec #define.
On peut toutefois utiliser cette dernière directive pour définir des constantes symboliques :
#define PI 3.14159265358
Dans les fichiers qui ont reçu cette définition, le préprocesseur remplacera la chaîne PI par sa valeur textuelle dans toutes ses occurrences qui n’apparaissent pas dans une chaîne de caractères.
#define PI 3.14
double x=PI;
char*a="Le savant grec Pythagore a découvert PI
sans calculette";
Le compilateur reçoit une version modifiée du dernier fragment :
double x=3.14;
char*a="Le savant grec Pythagore a découvert PI
sans calculette";
2. Le type void
Aucune variable ne peut être typée void, pourtant ce type entre dans la classification des types élémentaires...
Exercices pratiques
Voici trois variantes de l’algorithme de recherche d’une valeur maximale dans un ensemble de nombres représenté par un tableau, soit autant d’occasions de pratiquer les pointeurs et les références.
1. Rechercher la valeur maximale dans un tableau
a. Déclaration du tableau
Dans le corps de la fonction main(), un tableau d’entier est défini et initialisé sans allocation de mémoire par new ou malloc. Une variable de type entier indique le nombre de valeurs du tableau.
int tableau[] = {1, 10, -2, 0, 4, 11, -6};
int nombre_valeur = 7;
b. Implémentation de l’algorithme sous forme d’une fonction
La fonction C++ look_for_biggest reçoit comme paramètre le tableau et le nombre de valeurs à parcourir. Il faut noter la différence de typage entre la fonction main() où le tableau est int[] et la fonction de recherche acceptant comme paramètre un int*.
Le mot clé throw est une nouveauté, il déclenche une exception si le tableau ne comprend pas au moins une valeur.
L’algorithme utilise la valeur sentinelle, initialisée avec la première valeur du tableau, et qui est testée tour à tour avec toutes les valeurs du tableau.
// rechercher la plus grande valeur d'un tableau
int look_for_biggest(int* tab,int nb) ...