En langage C, les tableaux et les pointeurs sont intimement liés. En réalité, un tableau n'est rien d'autre qu'un pointeur constant vers son tout premier élément !
Comprendre cette équivalence fondamentale débloque une vision limpide de la manière dont le compilateur C manipule la mémoire vive et permet d'exploiter la technique ultra-rapide de l'arithmétique des pointeurs.
1. L'équivalence fondamentale : tab[i] == *(tab + i)#
Considérons un tableau d'entiers :
En C :
- Le symbole tab seul représente l'adresse mémoire du premier élément : tab == &tab[0].
- L'expression tab + 1 désigne l'adresse de la deuxième case : &tab[1].
- Déréférencer cette adresse *(tab + 1) extrait directement la valeur de la case : tab[1] (soit 20).
La notation avec les crochets tab[i] n'est en fait qu'un sucre syntaxique agréable inventé par Dennis Ritchie pour simplifier l'écriture de l'arithmétique de pointeurs *(tab + i) !
2. L'Arithmétique des pointeurs (Pointer Arithmetic)#
L'arithmétique sur les pointeurs ne fonctionne pas comme une addition mathématique ordinaire d'octets.
Lorsque vous ajoutez 1 à un pointeur, le processeur n'avance pas de 1 octet physique, mais d'un pas égal à la taille du type pointé (sizeof(*ptr)) :
- Pour un
char *(taille 1 octet) :ptr + 1avance de 1 octet. - Pour un
int *(taille 4 octets) :ptr + 1avance de 4 octets. - Pour un
double *(taille 8 octets) :ptr + 1avance de 8 octets.
flowchart LR
subgraph Memoire [Mémoire RAM pour int *ptr]
C0["Adresse 0x1000\nptr (Case 0)"] -->|+ 4 octets| C1["Adresse 0x1004\nptr + 1 (Case 1)"]
C1 -->|+ 4 octets| C2["Adresse 0x1008\nptr + 2 (Case 2)"]
end
#include <stdio.h>
int main(void)
{
int entiers[3] = {100, 200, 300};
int *p = entiers; // Équivaut à p = &entiers[0]
printf("Adresse p : %p | Valeur pointee : %d\n", (void*)p, *p);
printf("Adresse p + 1 : %p | Valeur pointee : %d\n", (void*)(p + 1), *(p + 1));
printf("Adresse p + 2 : %p | Valeur pointee : %d\n", (void*)(p + 2), *(p + 2));
return 0;
}
3. Parcourir un tableau avec un pointeur mobile#
Les développeurs C chevronnés parcourent souvent les tableaux en faisant glisser un pointeur d'une case à l'autre avec l'opérateur d'incrémentation ptr++ :
#include <stdio.h>
#define TAILLE 5
int main(void)
{
int notes[TAILLE] = {14, 18, 12, 16, 9};
int *curseur = notes; // Pointeur positionné au début du tableau
printf("Parcours du tableau avec pointeur mobile :\n");
for (int i = 0; i < TAILLE; i++)
{
printf("Element %d : %d (a l'adresse %p)\n", i, *curseur, (void*)curseur);
curseur++; // Avance d'exactement sizeof(int) = 4 octets vers la case suivante
}
return 0;
}
4. Soustraction de pointeurs et ptrdiff_t#
Il est possible de soustraire deux pointeurs pointant dans le même tableau : le résultat n'est pas un nombre d'octets, mais le nombre exact d'éléments qui les séparent :
int tab[10];
int *debut = &tab[2];
int *fin = &tab[7];
printf("Nombre d'elements entre fin et debut : %td\n", fin - debut); // Affiche 5 !
5. Les pointeurs et le qualificateur const#
Pour sécuriser votre code et empêcher des écritures accidentelles, le C permet de verrouiller un pointeur avec const selon trois configurations :
1. Pointeur vers une donnée constante (const int *ptr)#
La valeur pointée est en lecture seule (impossible de faire *ptr = 10), mais le pointeur peut changer d'adresse :
int x = 5, y = 10;
const int *ptr = &x;
// *ptr = 20; // ERREUR : La cible est constante
ptr = &y; // VALIDE : Le pointeur peut pointer vers une autre case
2. Pointeur constant vers une donnée variable (int * const ptr)#
L'adresse est figée (impossible de faire ptr = &y), mais la valeur pointée peut être modifiée :
int a = 5, b = 10;
int * const ptr = &a;
*ptr = 20; // VALIDE : On modifie la valeur de 'a'
// ptr = &b; // ERREUR : L'adresse du pointeur est verrouillée
Vous comprenez à présent comment naviguer en mémoire avec l'arithmétique des pointeurs. Découvrons dans le chapitre suivant le summum de la flexibilité en C : la gestion de la mémoire dynamique avec malloc et free.
Commentaires