Pointeurs et tableaux en langage C

21 Sep 2019 21 Sep 2019 39361 vues ESSADDOUKI Mostafa 7 min de lecture

Pointeurs et tableaux

En C, il existe une relation fondamentale entre les pointeurs et les tableaux : le nom d'un tableau est un pointeur constant vers son premier élément. Cette équivalence permet de parcourir et manipuler les tableaux via l'arithmétique des pointeurs.

Adresse de base d'un tableau Lorsqu'un tableau est déclaré, le compilateur alloue un bloc de mémoire contiguë et attribue une adresse de base — l'adresse du premier élément. Le nom du tableau est cette adresse : Tab est équivalent à &Tab[0].

Pour un tableau int Tab[4] = {3, 2, 7, 9} déclaré à l'adresse 2000 (2 octets par int) :

Représentation mémoire du tableau Tab : éléments à l'adresse 2000, 2002, 2004, 2006

ExpressionSignificationValeur (exemple)
TabAdresse de base (= &Tab[0])2000
Tab + iAdresse de Tab[i]2000 + i × sizeof(int)
*(Tab + i)Valeur de Tab[i]Tab[i]
&Tab[0]Adresse du premier élément2000
Équivalence fondamentaleCes deux notations sont strictement équivalentes en C :
Tab[i]      /* notation tableau  */
*(Tab + i)  /* notation pointeur — identique */
Le compilateur traduit Tab[i] en *(Tab + i) en interne.

1. Pointeur vers un tableau 1D


Syntaxe C
int Tab[N];
int *p;

p = Tab;          /* p pointe vers Tab[0] — équivalent à p = &Tab[0] */
*(p + i)          /* accès à Tab[i] via le pointeur p */
*(Tab + i)        /* accès à Tab[i] via le nom du tableau */

Exemple n°1 — Parcours par incrémentation de pointeur

#include <stdio.h>

int main(void)
{
    int Tab[4] = {3, 2, 7, 9};
    int *p = Tab;    /* p pointe vers Tab[0] */
    int i;

    for (i = 0; i < 4; i++) {
        printf("Tab[%d] = %d\n", i, *p);
        p++;         /* avance vers l'élément suivant */
    }

    return 0;
}
Sortie
Tab[0] = 3
Tab[1] = 2
Tab[2] = 7
Tab[3] = 9

Exemple n°2 — Parcours par arithmétique d'indice (sans modifier p)

#include <stdio.h>

int main(void)
{
    int Tab[4] = {3, 2, 7, 9};
    int i;

    for (i = 0; i < 4; i++) {
        printf("Tab[%d] : adresse = %p  |  valeur = %d\n",
               i, (void *)(Tab + i), *(Tab + i));
    }

    return 0;
}
Sortie
Tab[0] : adresse = 0x7ffd1000  |  valeur = 3
Tab[1] : adresse = 0x7ffd1004  |  valeur = 2
Tab[2] : adresse = 0x7ffd1008  |  valeur = 7
Tab[3] : adresse = 0x7ffd100c  |  valeur = 9
Attention — Tab est un pointeur constant Le nom du tableau (Tab) est un pointeur constant— son adresse ne peut pas être modifiée. Pour parcourir par incrémentation, il faut utiliser une copie dans un pointeur variable :
Tab++;          /* ERREUR — impossible de modifier Tab */

int *p = Tab;
p++;            /* correct — p est une variable */

2. Pointeur vers un tableau 2D

Pour un tableau 2D Tab[L][C], les éléments sont stockés en mémoire de manière contiguë ligne par ligne (row-major order). L'élément Tab[i][j] se trouve à l'offset i × C + j depuis le début du tableau.


Syntaxe — Accès via pointeur C
/* Méthode 1 — pointeur simple avec calcul d'offset */
int *p = (int *)Tab;
*(p + i * nb_colonnes + j)   /* équivalent à Tab[i][j] */

/* Méthode 2 — double déréférencement */
*(*(Tab + i) + j)            /* équivalent à Tab[i][j] */
NotationSignificationÉquivalent tableau
Tab + iAdresse de la ligne i&Tab[i]
*(Tab + i)Pointeur vers le 1er élément de la ligne iTab[i]
*(Tab + i) + jAdresse de l'élément [i][j]&Tab[i][j]
*(*(Tab + i) + j)Valeur de l'élément [i][j]Tab[i][j]

Exemple n°3 — Parcours d'un tableau 2D (méthode 1 — offset)

#include <stdio.h>

int main(void)
{
    int Tab[3][4] = {
        {1,  2,  3,  4},
        {5,  6,  7,  8},
        {9, 10, 11, 12}
    };
    int *p = (int *)Tab;   /* pointeur simple vers le début du tableau */
    int i, j;

    for (i = 0; i < 3; i++) {
        for (j = 0; j < 4; j++) {
            printf("Tab[%d][%d] = %2d\n", i, j, *(p + i * 4 + j));
        }
    }

    return 0;
}
Sortie (extrait)
Tab[0][0] =  1   Tab[0][1] =  2   Tab[0][2] =  3   Tab[0][3] =  4
Tab[1][0] =  5   Tab[1][1] =  6   Tab[1][2] =  7   Tab[1][3] =  8
Tab[2][0] =  9   Tab[2][1] = 10   Tab[2][2] = 11   Tab[2][3] = 12

Exemple n°4 — Parcours d'un tableau 2D (méthode 2 — double déréférencement)

#include <stdio.h>

int main(void)
{
    int Tab[3][4] = {
        {1,  2,  3,  4},
        {5,  6,  7,  8},
        {9, 10, 11, 12}
    };
    int i, j;

    for (i = 0; i < 3; i++) {
        for (j = 0; j < 4; j++) {
            printf("Tab[%d][%d] = %2d\n", i, j, *(*(Tab + i) + j));
        }
    }

    return 0;
}
Résumé des formules pour Tab[L][C]
MéthodeExpressionNote
Tableau standardTab[i][j]La plus lisible
Pointeur + offset*(p + i * C + j)p = (int *)Tab
Double déréférencement*(*(Tab + i) + j)Sans pointeur auxiliaire

3. Tableau de pointeurs

Tableau de pointeurs Un tableau de pointeurs est un tableau dont chaque élément est une adresse. Chaque case stocke un pointeur vers une variable ou un bloc mémoire indépendant — les données pointées ne sont pas nécessairement contiguës.

Syntaxe C
type *nom_tab[taille];   /* tableau de pointeurs vers type */

int  *Tab[5];            /* 5 pointeurs vers int */
char *noms[4];           /* 4 pointeurs vers char (4 chaînes) */

Exemple n°5 — Tableau de pointeurs vers des entiers

#include <stdio.h>

int main(void)
{
    int a = 3, b = 7, c = 1;
    int *Tab[3];

    Tab[0] = &a;
    Tab[1] = &b;
    Tab[2] = &c;

    for (int i = 0; i < 3; i++) {
        printf("Tab[%d] : adresse = %p  |  valeur = %d\n",
               i, (void *)Tab[i], *Tab[i]);
    }

    return 0;
}
Sortie
Tab[0] : adresse = 0x7ffd1010  |  valeur = 3
Tab[1] : adresse = 0x7ffd1014  |  valeur = 7
Tab[2] : adresse = 0x7ffd1018  |  valeur = 1

Exemple n°6 — Tableau de chaînes de caractères

#include <stdio.h>

int main(void)
{
    char *noms[4] = {
        "ESSADDOUKI Mostafa",
        "KAYOUH Mohamed",
        "ESSADDOUKI Ismail",
        "SEKHRA Omar"
    };

    for (int i = 0; i < 4; i++) {
        printf("noms[%d] = %s\n", i, noms[i]);
    }

    return 0;
}
Sortie
noms[0] = ESSADDOUKI Mostafa
noms[1] = KAYOUH Mohamed
noms[2] = ESSADDOUKI Ismail
noms[3] = SEKHRA Omar
Astuce — char*[] vs char[][] Un tableau char *noms[N] est plus flexible qu'un tableau char noms[N][L] : chaque chaîne peut avoir une longueur différente sans gaspillage mémoire. En revanche, les pointeurs pointent vers des chaînes littérales en mémoire lecture seule — pour des chaînes modifiables, utiliser char noms[N][L] ou allouer dynamiquement.

Exemple n°7 — Tableau 2D dynamique via tableau de pointeurs

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

int main(void)
{
    int L = 3, C = 4;
    int i, j;
    int *Tab[3];

    /* Allocation d'une ligne de 4 entiers pour chaque pointeur */
    for (i = 0; i < L; i++) {
        Tab[i] = (int *)malloc(C * sizeof(int));
        if (Tab[i] == NULL) { perror("malloc"); exit(1); }
    }

    /* Remplissage : Tab[i][j] = i * j */
    for (i = 0; i < L; i++)
        for (j = 0; j < C; j++)
            *(*(Tab + i) + j) = i * j;

    /* Affichage */
    for (i = 0; i < L; i++) {
        for (j = 0; j < C; j++)
            printf("Tab[%d][%d] = %2d\n", i, j, *(*(Tab + i) + j));
    }

    /* Libération */
    for (i = 0; i < L; i++) free(Tab[i]);

    return 0;
}
Sortie
Tab[0][0] =  0   Tab[0][1] =  0   Tab[0][2] =  0   Tab[0][3] =  0
Tab[1][0] =  0   Tab[1][1] =  1   Tab[1][2] =  2   Tab[1][3] =  3
Tab[2][0] =  0   Tab[2][1] =  2   Tab[2][2] =  4   Tab[2][3] =  6
ApprocheDéclarationMémoire contiguëTaille des lignesModifiable
Tableau 2D statiqueint Tab[L][C]OuiFixeOui
Tableau de pointeursint *Tab[L]Non (lignes indépendantes)VariableOui
Tableau de chaînes (littérales)char *Tab[N]NonVariableNon (lecture seule)

Récapitulatif

ExpressionSignificationÉquivalent
TabAdresse du 1er élément&Tab[0]
*(Tab + i)Valeur de l'élément iTab[i]
*(p + i * C + j)Élément [i][j] d'un tableau 2DTab[i][j]
*(*(Tab + i) + j)Élément [i][j] par double déréférencementTab[i][j]
int *Tab[N]Tableau de N pointeurs vers int
char *Tab[N]Tableau de N chaînes

Discussion (0)

Soyez le premier à laisser un commentaire !

Laisser un commentaire

Votre commentaire sera visible après modération.