les pointeurs en C

15 Sep 2017 15 Sep 2017 23098 vues ESSADDOUKI Mostafa 10 min de lecture

Les pointeurs en langage C

Un pointeur est une variable spéciale qui stocke l'adresse mémoire d'une autre variable. Chaque pointeur est lié à un type de données précis : un pointeur sur int ne peut pointer que vers un entier, un pointeur sur float vers un flottant, etc.

Définition — Pointeur Un pointeur est une variable dont la valeur est une adresse mémoire. Il permet d'accéder indirectement à la variable stockée à cette adresse. Deux opérateurs fondamentaux lui sont associés :
  • & — opérateur d'adresse : retourne l'adresse d'une variable
  • * — opérateur de déréférencement : accède à la valeur stockée à une adresse

1. Les deux opérateurs fondamentaux

OpérateurNomRôleExemple
&Adresse deRetourne l'adresse mémoire d'une variable&x → adresse de x
* (déclaration)PointeurDéclare une variable de type pointeurint *ptr
* (usage)DéréférencementAccède à la valeur pointée*ptr → valeur à l'adresse

Exemple n°1 — Afficher l'adresse d'une variable

#include <stdio.h>

int main(void)
{
    int x;
    printf("adresse de x est : %p\n", &x);
    return 0;
}
Sortie
adresse de x est : 0x7ffee6ffea28
Remarque — Format %p Le format %p de printf est dédié à l'affichage des adresses mémoire en hexadécimal. La valeur exacte dépend du système et varie à chaque exécution.

Exemple n°2 — Déclarer un pointeur et lui affecter une adresse

#include <stdio.h>

int main(void)
{
    int x = 10;

    /* int *ptr : ptr est un pointeur vers un entier */
    int *ptr;

    /* ptr reçoit l'adresse de x */
    ptr = &x;

    printf("x    = %d\n",  x);
    printf("&x   = %p\n",  &x);
    printf("ptr  = %p\n",  ptr);   // même adresse que &x
    printf("*ptr = %d\n",  *ptr);  // valeur à cette adresse = 10

    return 0;
}
Sortie
x    = 10
&x   = 0x7ffee6ffea28
ptr  = 0x7ffee6ffea28
*ptr = 10

Exemple n°3 — Modifier une variable via son pointeur

#include <stdio.h>

int main(void)
{
    int x = 2;
    int *ptr = &x;   /* ptr pointe sur x */

    *ptr = 5;        /* équivaut à : x = 5 */

    printf("*ptr = %d\n", *ptr);
    printf("x    = %d\n", x);

    return 0;
}
Sortie
*ptr = 5
x    = 5

2. Déclaration d'un pointeur


Syntaxe — Déclaration C
type *nom_du_pointeur;

/* Exemples */
char  *buffer;   /* pointeur vers un caractère */
int   *px;       /* pointeur vers un entier    */
float *py;       /* pointeur vers un flottant  */
Danger — Pointeur non initialiséUn pointeur déclaré mais non initialisé contient une adresse aléatoire. L'utiliser provoque un comportement indéfini (crash, corruption mémoire). Toujours initialiser un pointeur avant de le déréférencer :
int *p;
*p = 5;   /* comportement indéfini — p pointe nulle part */

/* Correct : initialiser d'abord */
int x;
int *p = &x;
*p = 5;   /* valide */

3. Initialisation d'un pointeur

Un pointeur doit pointer vers une zone mémoire réservée par le compilateur. Il existe deux façons valides d'initialiser un pointeur.

MéthodeDescriptionLibération requise
Adresse d'une variablep = &variable;Non — gérée automatiquement
Allocation dynamiquep = (int*)malloc(sizeof(int));Oui — appeler free(p)
Pointeur nulp = 0; ou p = NULL;Non — indique "ne pointe nulle part"

Exemple n°4 — Initialisation statique et dynamique

#include <stdio.h>
#include <stdlib.h>

int main(void)
{
    int f;
    int *pf, *pg;

    /* Initialisation statique — pointe vers une variable existante */
    pf = &f;
    *pf = 42;
    printf("*pf = %d\n", *pf);

    /* Initialisation dynamique — allocation d'un entier en mémoire */
    pg = (int*)malloc(sizeof(int));
    *pg = 100;
    printf("*pg = %d\n", *pg);

    /* Libération de la mémoire allouée dynamiquement */
    free(pg);

    /* Pointeur nul — indique qu'il ne pointe nulle part */
    pg = 0;   /* ou NULL */

    return 0;
}
Sortie
*pf = 42
*pg = 100

4. Utilisation des pointeurs

Une fois déclaré et initialisé, on accède au contenu de l'adresse pointée via l'opérateur *. Il est important de distinguer *P et &P :

ExpressionSignificationExemple avec P = &A
PL'adresse stockée dans PAdresse de A
*PLa valeur à l'adresse pointéeValeur de A
&PL'adresse du pointeur lui-mêmeAdresse de la variable P

Exemple n°5 — Opérations via pointeur

int  *P;
int   A = 10;

P = &A;          /* P pointe sur A */

*P = *P + 1;     /* équivaut à : A = A + 1  → A = 11 */
*P = 5;          /* équivaut à : A = 5 */

Schéma — relation entre pointeur P et variable A en mémoire

5. Opérations élémentaires sur les pointeurs

Priorité des opérateurs

Les opérateurs * et & ont la même priorité que les autres opérateurs unaires (!, ++, --). Dans une même expression, les opérateurs unaires sont évalués de droite à gauche.

Arithmétique de pointeur

Un ensemble limité d'opérations arithmétiques peut être effectué sur des pointeurs :

OpérationSyntaxeSignification
IncrémenterP++P pointe sur l'élément suivant
DécrémenterP--P pointe sur l'élément précédent
Ajouter un entierP + n ou P += nP pointe n éléments plus loin
Soustraire un entierP - n ou P -= nP pointe n éléments en arrière
Soustraire deux pointeursP2 - P1Nombre d'éléments entre P1 et P2
Remarque — Sens de l'arithmétique L'arithmétique de pointeur n'a de sens que sur un tableau. Additionner deux adresses n'a aucune signification. Soustraire deux adresses donne le décalage en nombre d'éléments entre elles.

Exemple n°6 — Opérations arithmétiques sur pointeur

int x, y;
int *P;

P = &x;

y   = *P + 1;    /* y = x + 1    (lit la valeur pointée) */
*P  = *P + 10;   /* x = x + 10   (modifie via pointeur)  */
*P += 2;         /* x += 2                               */
(*P)++;          /* x++  — parenthèses OBLIGATOIRES      */
Danger — (*P)++ vs *P++ Les parenthèses sont indispensables pour incrémenter la valeur pointée. Sans elles, c'est le pointeurlui-même qui est incrémenté :
(*P)++   /* incrémente la valeur à l'adresse — correct     */
*P++     /* incrémente le pointeur P — comportement inattendu */

Exemple n°7 — Copier un pointeur (deux pointeurs sur la même variable)

#include <stdio.h>

int main(void)
{
    int x = 5;
    int *p1, *p2;

    p2 = &x;
    p1 = p2;   /* p1 et p2 pointent maintenant sur x */

    printf("*p1 = %d\n", *p1);
    printf("*p2 = %d\n", *p2);

    /* Modifier via p1 change aussi ce que voit p2 */
    *p1 = 99;
    printf("après *p1=99 : *p2 = %d\n", *p2);

    return 0;
}
Sortie
*p1 = 5
*p2 = 5
après *p1=99 : *p2 = 99
Remarque — Pointeur nul La valeur 0 (ou NULL) est la seule valeur numérique qu'on peut affecter directement à un pointeur. Elle signifie que le pointeur ne pointe nulle partet sert à indiquer l'absence de valeur valide :
int *P = 0;      /* ou NULL — pointeur nul */
if (P != NULL)   /* toujours vérifier avant déréférencement */
    *P = 5;

6. Pointeurs et tableaux

a. Tableau à une dimension

Définition — Nom du tableau = pointeur constant Le nom d'un tableau est un pointeur constant sur son premier élément. Ainsi, pour un tableau A, A équivaut à &A[0]. La différence essentielle avec un pointeur ordinaire est que le nom d'un tableau ne peut pas être modifié (constante).
ExpressionÉquivalentDescription
A&A[0]Adresse du 1er élément
A+i&A[i]Adresse du i-ème élément
*(A+i)A[i]Valeur du i-ème élément
P = AP = &A[0]P pointe sur le début du tableau
*(P+i)A[i]Accès au i-ème élément via pointeur

Schéma — lien entre tableau A et pointeur P en mémoire

Attention — Pointeur vs nom de tableau
OpérationPointeur PNom de tableau A
P = APermisNon permis (A = P interdit)
P++PermisNon permis (A++ interdit)
*(P+i)PermisPermis (*(A+i))

Exemple n°8 — Initialiser un tableau à 1 — trois versions

/* Version 1 — sans pointeur */
int main(void)
{
    int Tab[10], i;
    for (i = 0; i < 10; i++)
        Tab[i] = 1;
    return 0;
}

/* Version 2 — avec pointeur et indice */
int main(void)
{
    int Tab[10], i;
    int *P = Tab;
    for (i = 0; i < 10; i++)
        *(P + i) = 1;
    return 0;
}

/* Version 3 — avec pointeur incrémenté */
int main(void)
{
    int Tab[10];
    int *P;
    for (P = Tab; P < (Tab + 10); P++)
        *P = 1;
    return 0;
}
Astuce — Version 3 : la plus idiomatique La version 3 est la plus courante en C : le pointeur P parcourt directement les cases du tableau sans variable d'indice. La condition P < Tab + 10 exploite l'arithmétique de pointeur pour détecter la fin.

b. Tableau à deux dimensions

Définition — Tableau 2D et pointeurs Un tableau à deux dimensions est un tableau de tableaux unidimensionnels. Son nom est un pointeur de pointeurs : il pointe sur le premier tableau-ligne. En général : Tab[i][j] est équivalent à *(*(Tab + i) + j).

Schéma — tableau 2D TAB[6][7] en mémoire

Exemple n°9 — Accès aux éléments d'un tableau 2D via pointeurs

int Tab[3][4] = {{ 0,  1,  2,  3},
                 {10, 11, 12, 13},
                 {20, 21, 22, 23}};

/* Tab      → adresse de Tab[0] = {0,1,2,3}   */
/* Tab + 1  → adresse de Tab[1] = {10,11,12,13} */
/* Tab[i][j] = *(*(Tab + i) + j)               */

Schéma — stockage en mémoire d'un tableau 2D (rangée majeure)

Stockage en mémoire — ordre ligne-majeur La mémoire étant linéaire, un tableau 2D est stocké ligne par ligne (row-major order) : toutes les colonnes de la ligne 0, puis de la ligne 1, etc. Les éléments sont donc tous adjacents en mémoire, ce qui permet d'y accéder avec un simple pointeur.

Exemple n°10 — Initialiser un tableau 2D à 1 — trois versions

/* Version 1 — sans pointeur */
int main(void)
{
    int Tab[5][4], i, j;
    for (i = 0; i < 5; i++)
        for (j = 0; j < 4; j++)
            Tab[i][j] = 1;
    return 0;
}

/* Version 2 — tableau de pointeurs *P[5] */
int main(void)
{
    int Tab[5][4];
    int *P[5];   /* tableau de 5 pointeurs — un par ligne */
    int i, j;

    /* Chaque P[i] pointe sur le début de la ligne i */
    for (i = 0; i < 5; i++)
        P[i] = Tab[i];

    for (i = 0; i < 5; i++)
        for (j = 0; j < 4; j++)
            *(*(P + i) + j) = 1;   /* équivaut à P[i][j] = 1 */
    return 0;
}

/* Version 3 — pointeur simple sur la mémoire linéaire */
int main(void)
{
    int Tab[5][4];
    int *P = Tab[0];   /* pointe sur le 1er élément */
    int i;

    /* Les 5*4 = 20 éléments sont adjacents en mémoire */
    for (i = 0; i < 5 * 4; i++)
        *(P + i) = 1;
    return 0;
}
Astuce — Version 3 : exploitation de l'adjacence mémoire Puisque tous les éléments d'un tableau 2D sont contigus en mémoire, un seul pointeur suffit pour les parcourir tous. La version 3 est plus concise et généralement plus rapide que la double boucle.

Récapitulatif

ConceptSyntaxeDescription
Déclarationint *p;p est un pointeur vers un entier
Initialisationp = &x;p reçoit l'adresse de x
Déréférencement*pValeur à l'adresse pointée
Modification*p = 5;Modifie la variable originale
Incrément valeur(*p)++Incrémente la valeur (parenthèses obligatoires)
Tableau 1D*(p+i) = A[i]Accès au i-ème élément
Tableau 2D*(*(p+i)+j) = A[i][j]Accès à l'élément ligne i, colonne j
Pointeur nulp = NULL;Indique l'absence de valeur valide

Discussion (0)

Soyez le premier à laisser un commentaire !

Laisser un commentaire

Votre commentaire sera visible après modération.