La question de la taille d'un tableau en C revient dès qu’il faut parcourir, valider ou transmettre des données. Le piège, c’est que la bonne réponse dépend du contexte: tableau statique, paramètre de fonction, chaîne de caractères ou mémoire allouée dynamiquement. Je vais aller droit au point avec les méthodes fiables, les cas qui trompent même les développeurs expérimentés et la façon la plus propre de structurer ce calcul.
Les points essentiels pour lire la taille d’un tableau sans piège
- `sizeof(tab) / sizeof(tab[0])` donne le nombre d’éléments seulement si `tab` est un vrai tableau dans le même scope.
- `sizeof` renvoie des octets, pas des éléments.
- Dans une fonction, un tableau reçu en paramètre se comporte comme un pointeur, donc sa taille réelle n’est plus disponible.
- Pour un tableau dynamique, il faut stocker la longueur séparément.
- Pour une chaîne C, `strlen` mesure la longueur de texte avant le `\0`, pas la capacité du tableau.
- Avec un tableau 2D, il faut raisonner en lignes et en colonnes, pas seulement en taille totale.
Pourquoi `sizeof` donne la bonne valeur sur un vrai tableau
Dans un tableau déclaré comme objet local ou global, la méthode la plus simple reste la plus solide: diviser la taille totale en octets par la taille d’un élément. C’est exactement ce que fait `sizeof(tab) / sizeof(tab[0])`.
#include
int main(void) {
int notes[] = {12, 15, 9, 18, 14};
size_t n = sizeof(notes) / sizeof(notes[0]);
printf("Nombre d'éléments : %zu\n", n);
return 0;
}
Ici, `sizeof(notes)` mesure tout le bloc mémoire occupé par le tableau, tandis que `sizeof(notes[0])` mesure un seul `int`. Le résultat est donc le nombre d’éléments, et non un volume en octets. C’est la formule que je recommande dans 90 % des cas simples, parce qu’elle reste correcte même si le type des éléments change ensuite. La limite apparaît dès qu’on sort du scope du tableau, et c’est là que beaucoup de code casse.
Le bon réflexe, c’est donc de distinguer taille en octets et nombre d’éléments. Une fois ce point clair, on peut regarder pourquoi la règle cesse de fonctionner au premier passage en fonction.
Pourquoi `sizeof` cesse d’être fiable dans une fonction

En C, quand on passe un tableau à une fonction, il subit une conversion implicite vers un pointeur sur son premier élément. Autrement dit, la fonction ne reçoit pas le tableau complet, mais seulement une adresse. C’est pour cela que `sizeof(tab)` dans la fonction ne renvoie plus la taille du tableau, mais celle du pointeur.
#include
void afficher_taille(int tab[]) {
printf("%zu\n", sizeof(tab)); /* taille du pointeur, pas du tableau */
}
int main(void) {
int a[] = {1, 2, 3, 4};
afficher_taille(a);
return 0;
}
Ce point est fondamental: dans une signature de fonction, `int tab[]` et `int *tab` reviennent au même pour le paramètre. Le tableau a perdu son information de longueur. Je vois souvent ce bug dans des fonctions utilitaires qui veulent “deviner” la taille d’un buffer, alors qu’elles n’en ont tout simplement pas les moyens.
| Situation | Ce que voit `sizeof` | Bonne pratique |
|---|---|---|
| Tableau local `int a[10]` | La taille totale du tableau | Utiliser `sizeof(a) / sizeof(a[0])` |
| Paramètre `int a[]` dans une fonction | La taille d’un pointeur | Passer aussi la longueur en paramètre |
| Tableau dynamique `malloc` | La taille du pointeur | Conserver la taille dans une variable ou une structure |
La conséquence pratique est simple: si une fonction doit parcourir un tableau, elle doit recevoir le pointeur et la longueur. C’est ce contrat explicite qui évite les suppositions dangereuses.
Compter les éléments sans se tromper
Pour le nombre d’éléments, la formule canonique reste `sizeof(tab) / sizeof(tab[0])`. Je préfère cette écriture à `sizeof(tab) / sizeof(int)` parce qu’elle s’adapte automatiquement au type réel du tableau. Si le tableau passe de `int` à `double`, le code continue de fonctionner sans modification.
double mesures[] = {1.2, 3.4, 5.6, 7.8};
size_t n = sizeof(mesures) / sizeof(mesures[0]);
Il faut aussi éviter une confusion très fréquente: `strlen` ne sert pas à mesurer un tableau. Elle calcule la longueur d’une chaîne C, donc le nombre de caractères avant le caractère nul `'\0'`. C’est utile pour du texte, pas pour un tableau d’entiers, de flottants ou de structures.
- `sizeof(tab)` mesure des octets.
- `sizeof(tab[0])` mesure un élément.
- `sizeof(tab) / sizeof(tab[0])` donne le nombre d’éléments.
- `strlen(tab)` compte les caractères d’une chaîne terminée par `'\0'`.
Si je devais résumer la règle en une ligne, ce serait celle-ci: on compte les éléments avec `sizeof`, on compte les caractères avec `strlen`. La suite devient plus intéressante dès qu’on quitte les tableaux d’une seule dimension.
Les tableaux 2D demandent de raisonner en lignes et en colonnes
Avec un tableau à deux dimensions, la logique reste la même, mais on doit séparer le calcul des lignes et celui des colonnes. Sur un tableau statique, les deux dimensions sont connues au moment de la compilation.
int grille[3][4];
size_t lignes = sizeof(grille) / sizeof(grille[0]);
size_t colonnes = sizeof(grille[0]) / sizeof(grille[0][0]);
Ici, `grille` contient 3 lignes de 4 entiers. Cette méthode est très utile pour boucler proprement, générer des matrices de travail ou traiter des données tabulaires en local. En revanche, elle ne survit pas au passage en fonction si on déclare le paramètre de manière trop générale.
void traiter(int grille[][4], size_t lignes) {
for (size_t i = 0; i < lignes; ++i) {
for (size_t j = 0; j < 4; ++j) {
/* ... */
}
}
}
Le point à retenir est net: dans une fonction, la dimension des colonnes doit souvent rester connue, alors que le nombre de lignes peut être transmis séparément. Si vous travaillez avec une matrice aplatie, par exemple un buffer `rows * cols`, il faut alors stocker les deux dimensions et indexer manuellement avec `i * cols + j`.
Quand la mémoire est dynamique, la taille doit voyager avec les données
Avec `malloc`, `calloc` ou `realloc`, on ne parle plus d’un tableau statique mais d’une zone mémoire allouée dynamiquement. Le langage ne conserve pas automatiquement le nombre d’éléments associés au pointeur. Je considère donc qu’un buffer dynamique sans longueur explicite est un mauvais contrat d’API.
#include
typedef struct {
int *data;
size_t size;
} IntBuffer;
Cette approche est plus lisible qu’un pointeur isolé, parce qu’elle force le code appelant à gérer la taille. Elle aide aussi à sécuriser les parcours et les réallocations. Quand le buffer grossit ou rétrécit, on met à jour la valeur `size` au même endroit, au lieu de la recomposer à partir d’un pointeur qui ne sait rien.
Dans des projets plus avancés, on peut aussi encapsuler les données dans une structure plus riche, avec capacité, longueur utile et éventuellement espace réservé. C’est souvent mieux qu’un simple tableau brut dès que le code devient partageable entre plusieurs modules. Et c’est justement là que les erreurs de taille commencent à coûter cher.
Les erreurs que je corrige le plus souvent
Les bugs liés à la taille d’un tableau sont rarement spectaculaires au début. Ils se cachent derrière un parcours incomplet, un dépassement de limite ou une valeur absurde affichée en débogage. Je revois toujours les mêmes erreurs, avec les mêmes causes.
| Erreur | Pourquoi elle casse | Ce qu’il faut faire |
|---|---|---|
| Utiliser `sizeof(ptr)` comme taille d’un tableau | Le pointeur n’est pas le tableau | Conserver la longueur séparément |
| Employer `strlen` sur un tableau non textuel | `strlen` s’arrête à `'\0'` | Utiliser `sizeof` pour un vrai tableau |
| Coder une longueur en dur | Le tableau évolue, la constante oublie de suivre | Calculer la taille automatiquement quand c’est possible |
| Oublier `size_t` | Le type de taille le plus adapté est signé ou tronqué selon le contexte | Utiliser `size_t` pour les tailles et indices de parcours |
Je fais aussi attention aux macros “magiques” censées déduire la taille à la place du programmeur. Elles peuvent fonctionner sur un tableau local, mais elles deviennent vite trompeuses si on leur passe un pointeur. À mes yeux, une règle simple et explicite reste plus robuste qu’une astuce trop automatique.
La méthode que j’utilise selon le cas réel
Si je dois donner une réponse courte et exploitable, je la formule ainsi: le tableau statique se mesure avec `sizeof`, le tableau passé en fonction se transmet avec sa taille, et le tableau dynamique garde sa longueur à côté de ses données. C’est la seule logique qui reste fiable dans un code qui évolue.
- Tableau local ou global: `sizeof(tab) / sizeof(tab[0])`.
- Tableau reçu par une fonction: `tab` + `size_t n`.
- Tableau 2D statique: calculer lignes et colonnes séparément.
- Chaîne C: `strlen` uniquement pour la longueur textuelle.
- Mémoire dynamique: stocker la taille dans une structure ou une variable dédiée.
En pratique, je garde une règle de rédaction très simple pour le code C: ne jamais laisser une fonction “deviner” la taille d’un buffer qu’on lui a déjà fait perdre en route. C’est moins élégant qu’un calcul implicite, mais infiniment plus sûr quand le projet grossit et que plusieurs personnes touchent au même code.