les tableaux en C

25 Aug 2019 25 Aug 2019 13117 vues ESSADDOUKI Mostafa 9 min de lecture

Les tableaux en langage C

Un tableau est une collection d'éléments du même type stockés dans des emplacements mémoire contigus. Il est identifié par un nom et chaque élément est accessible via un indice entier.

Définition — Tableau Un tableau est une structure de données homogène qui regroupe un nombre fixe d'éléments de même type, stockés de façon adjacente en mémoire. L'accès à un élément se fait en temps constant via son indice, qui commence à 0 en C.

Schéma — tableau d'éléments contigus en mémoire

Pourquoi utiliser un tableau ? Avec des variables ordinaires (v1, v2, v3…), gérer un grand nombre d'éléments devient rapidement ingérable. Un tableau permet de représenter n éléments sous un seul nom et de les traiter efficacement avec des boucles.

1. Déclaration d'un tableau


Syntaxe — Déclaration d'un tableau C
type nom[taille];                        /* taille fixe */
type nom[] = { val1, val2, ... };        /* taille déduite */
type nom[taille] = { val1, val2, ... };  /* taille + initialisation */
FormeExempleRésultat
Taille seuleint tab[10];Tableau de 10 entiers, non initialisé
Taille variableint n=10; int T[n];Tableau à taille définie à l'exécution (VLA)
Initialisation seuleint tab[] = {10,20,30,40};Taille déduite automatiquement : 4
Taille + initialisationint tab[6] = {10,20,30,40};4 valeurs fournies, les 2 restantes valent 0

Exemple n°1 — Les trois formes de déclaration

int main(void)
{
    /* Forme 1 — taille seule */
    int tab[10];

    /* Forme 2 — taille variable (VLA, C99) */
    int n = 10;
    int T[n];

    /* Forme 3 — initialisation, taille déduite */
    int tab2[] = {10, 20, 30, 40};
    /* équivalent à : int tab2[4] = {10, 20, 30, 40} */

    /* Forme 4 — taille + initialisation partielle */
    int tab3[6] = {10, 20, 30, 40};
    /* équivalent à : int tab3[6] = {10, 20, 30, 40, 0, 0} */

    return 0;
}
Astuce — Initialisation complète à zéroPour initialiser tous les éléments à zéro, fournir une liste vide ou un seul zéro suffit — le compilateur complète automatiquement :
int tab[100] = {0};   /* tous les éléments valent 0 */
int tab[100] = {};    /* idem — valable en C99/C11  */

2. Propriétés d'un tableau

a. Accès aux éléments

Les éléments sont accessibles via un indice entier compris entre 0 et taille − 1.

Exemple n°2 — Lecture et modification d'éléments

#include <stdio.h>

int main(void)
{
    int tab[] = {10, 20, 30, 40};

    tab[2] = 5;           /* tab = {10, 20, 5, 40}  */
    tab[0] = 2;           /* tab = {2,  20, 5, 40}  */
    tab[2] = tab[1];      /* tab = {2,  20, 20, 40} */

    printf("%d - %d - %d - %d\n", tab[0], tab[1], tab[2], tab[3]);

    return 0;
}
Sortie
2 - 20 - 20 - 40

b. Absence de contrôle des indices

Le C ne vérifie pas si un indice est dans les bornes du tableau. Accéder hors limites compile sans erreur mais produit un comportement indéfini à l'exécution.

Exemple n°3 — Accès hors limites

#include <stdio.h>

int main(void)
{
    int tab[2];

    printf("%d\n", tab[3]);    /* hors limites — valeur imprévisible */
    printf("%d\n", tab[-2]);   /* indice négatif — comportement indéfini */

    return 0;
}
Sortie
-434996632
32766
Danger — Dépassement de tableau (buffer overflow)Accéder ou écrire hors des bornes d'un tableau est l'une des sources les plus fréquentes de bugs et de failles de sécurité en C. Toujours vérifier les indices avant tout accès :
#define TAILLE 10
int tab[TAILLE];
int i = 12;

/* Vérification correcte avant accès */
if (i >= 0 && i < TAILLE)
    printf("%d\n", tab[i]);

c. Initialisation avec trop d'éléments

Exemple n°4 — Plus d'initialiseurs que la taille

#include <stdio.h>

int main(void)
{
    int tab[2] = {10, 20, 30, 40, 50};   /* 5 valeurs pour 2 cases */
    return 0;
}
Sortie
warning: excess elements in array initializer
   int tab[2] = {10, 20, 30, 40, 50};
                        ^~
1 warning generated.
Attention — Warning, pas d'erreur Ce code compile mais génère un avertissement. Les éléments excédentaires sont ignorés. Toujours faire correspondre la taille déclarée au nombre d'initialiseurs.

d. Stockage contigu en mémoire

Les éléments d'un tableau sont stockés dans des adresses consécutives. L'écart entre deux adresses successives est égal à sizeof(type).

Exemple n°5 — Adresses consécutives

#include <stdio.h>

int main(void)
{
    int tab[5], i;

    printf("Taille d'un int : %lu octet(s)\n", sizeof(int));

    for (i = 0; i < 5; i++)
        printf("Adresse tab[%d] : %p\n", i, &tab[i]);

    return 0;
}
Sortie
Taille d'un int : 4 octet(s)
Adresse tab[0] : 0x7ffee793ba00
Adresse tab[1] : 0x7ffee793ba04   (+4)
Adresse tab[2] : 0x7ffee793ba08   (+4)
Adresse tab[3] : 0x7ffee793ba0c   (+4)
Adresse tab[4] : 0x7ffee793ba10   (+4)

3. Différences entre tableau et pointeur

Bien que le nom d'un tableau se comporte souvent comme un pointeur, il existe des différences fondamentales à connaître.

AspectTableau tab[]Pointeur *ptr
sizeofTaille totale du tableau (octets)Taille du pointeur (4 ou 8 octets)
Affectation d'adresseInterdit — tab = &x → erreurAutorisé — ptr = &x
ArithmétiqueInterdit — tab++ → erreurAutorisé — ptr++
Accès élémentstab[i] = *(tab+i)ptr[i] = *(ptr+i)
NatureConstante (adresse fixe)Variable (peut changer)

a. Opérateur sizeof

Exemple n°6 — sizeof sur tableau vs pointeur

#include <stdio.h>

int main(void)
{
    int tab[5] = {10, 20, 30, 40, 50};
    int *ptr    = tab;

    printf("sizeof(tab) = %lu\n",  sizeof(tab));   /* 5 × 4 = 20 octets */
    printf("sizeof(ptr) = %lu\n",  sizeof(ptr));   /* taille d'un pointeur = 8 */
    printf("Nb éléments = %lu\n",  sizeof(tab) / sizeof(tab[0]));  /* 5 */

    return 0;
}
Sortie
sizeof(tab) = 20
sizeof(ptr) = 8
Nb éléments = 5
Astuce — Calculer le nombre d'éléments Le nombre d'éléments d'un tableau se calcule avec sizeof(tab) / sizeof(tab[0]). Cette formule est plus robuste qu'une constante codée en dur car elle s'adapte automatiquement si la taille change :
#define NB_ELEM(t) (sizeof(t) / sizeof((t)[0]))

int tab[] = {1, 2, 3, 4, 5};
for (int i = 0; i < NB_ELEM(tab); i++)
    printf("%d ", tab[i]);

b. Affectation d'adresse

Exemple n°7 — Affectation interdite sur un tableau

#include <stdio.h>

int main(void)
{
    int tab[5] = {10, 20, 30, 40, 50};
    int x = 5;

    int *ptr = &x;   /* autorisé — ptr est une variable */
    tab = &x;        /* erreur de compilation           */

    return 0;
}
Sortie
error: array type 'int [5]' is not assignable
   tab = &x;
   ~~~ ^

c. Arithmétique sur tableau vs pointeur

Exemple n°8 — Incrément interdit sur le nom du tableau

int main(void)
{
    int a[10];
    int *p = a;

    p++;   /* autorisé — p est un pointeur variable */
    a++;   /* erreur de compilation — a est constant */

    return 0;
}
Sortie
error: cannot increment value of type 'int [10]'
   a++;
   ~^

4. Similitudes entre tableau et pointeur

a. Le nom du tableau = adresse du premier élément

Exemple n°9 — Nom du tableau utilisé comme pointeur

#include <stdio.h>

int main(void)
{
    int tab[] = {10, 20, 30, 40, 50, 60};
    int *ptr   = tab;   /* équivaut à ptr = &tab[0] */

    printf("Premier élément via tab : %d\n",  *tab);
    printf("Premier élément via ptr : %d\n",  *ptr);
    printf("Même adresse ?          : %d\n",  tab == ptr);

    return 0;
}
Sortie
Premier élément via tab : 10
Premier élément via ptr : 10
Même adresse ?          : 1

b. Accès aux éléments — quatre notations équivalentes

Le compilateur traduit tab[i] en *(tab + i) en interne. Les quatre notations suivantes sont donc strictement équivalentes.

Exemple n°10 — Quatre façons d'accéder au même élément

#include <stdio.h>

int main(void)
{
    int tab[] = {10, 20, 30, 40, 50, 60};
    int *ptr   = tab;

    printf("tab[2]      = %d\n",  tab[2]);       /* notation tableau classique */
    printf("*(tab + 2)  = %d\n",  *(tab + 2));   /* arithmétique via tableau   */
    printf("ptr[2]      = %d\n",  ptr[2]);        /* notation tableau via ptr   */
    printf("*(ptr + 2)  = %d\n",  *(ptr + 2));   /* arithmétique via pointeur  */

    return 0;
}
Sortie
tab[2]      = 30
*(tab + 2)  = 30
ptr[2]      = 30
*(ptr + 2)  = 30

c. Passage de tableau à une fonction

Quand un tableau est passé à une fonction, il est automatiquement converti en pointeur vers son premier élément — même si la syntaxe utilise des crochets. La fonction reçoit donc un pointeur, pas une copie du tableau.

Exemple n°11 — Passage de tableau : le paramètre devient un pointeur

#include <stdio.h>

/* "int ptr[]" et "int *ptr" sont strictement équivalents ici */
void afficher(int ptr[], int taille)
{
    int i;
    /* sizeof(ptr) donne la taille du POINTEUR, pas du tableau ! */
    printf("sizeof(ptr) dans la fonction = %lu\n", sizeof(ptr));

    for (i = 0; i < taille; i++)
        printf("%d ", ptr[i]);
    printf("\n");
}

int main(void)
{
    int tab[] = {10, 20, 30, 40, 50, 60};
    int n     = sizeof(tab) / sizeof(tab[0]);   /* calculé dans main */

    printf("sizeof(tab) dans main = %lu\n", sizeof(tab));
    afficher(tab, n);

    return 0;
}
Sortie
sizeof(tab) dans main       = 24
sizeof(ptr) dans la fonction = 8
10 20 30 40 50 60
Attention — Perte de taille lors du passage en fonction sizeof(tableau) dans main donne la taille réelle, mais dans une fonction appelée, le tableau devient un pointeur et sizeof ne retourne que la taille du pointeur (4 ou 8 octets). Toujours passer la taille explicitement en paramètre supplémentaire.

Récapitulatif

ConceptSyntaxe / RèglePoint clé
Déclarationint tab[n];Indices de 0 à n−1
Initialisationint tab[] = {1,2,3};Taille déduite automatiquement
Accèstab[i] = *(tab+i)Quatre notations équivalentes
Taillesizeof(tab)/sizeof(tab[0])Valable uniquement dans la portée de déclaration
Hors bornesAucune vérification automatiqueComportement indéfini — vérifier manuellement
Nom = adressetab = &tab[0]Constante — ne peut pas être modifiée
Passage en fonctionConverti en pointeur automatiquementPasser la taille séparément

Discussion (0)

Soyez le premier à laisser un commentaire !

Laisser un commentaire

Votre commentaire sera visible après modération.