Array declaration
Array est un type constitué d'une séquence non vide d'objets alloués de manière contiguë avec un element type particulier. Le nombre de ces objets (la taille du tableau) ne change jamais pendant la durée de vie du tableau.
Table des matières |
Syntaxe
Dans la grammaire de déclaration d'une déclaration de tableau, la séquence du spécificateur de type désigne le type d'élément (qui doit être un type d'objet complet), et le déclarateur a la forme :
[
static
(facultatif)
qualificateurs
(facultatif)
expression
(facultatif)
]
attr-spec-seq
(facultatif)
|
(1) | ||||||||
[
qualificateurs
(facultatif)
static
(facultatif)
expression
(facultatif)
]
attr-spec-seq
(facultatif)
|
(2) | ||||||||
[
qualificateurs
(facultatif)
*
]
attr-spec-seq
(facultatif)
|
(3) | ||||||||
| expression | - | toute expression autre que l'opérateur virgule , désigne le nombre d'éléments dans le tableau |
| qualifiers | - |
toute combinaison des qualificateurs
const
,
restrict
, ou
volatile
, uniquement autorisée dans les listes de paramètres de fonction ; ceci qualifie le type de pointeur vers lequel ce paramètre tableau est transformé
|
| attr-spec-seq | - | (C23) liste optionnelle d' attributs , appliquée au tableau déclaré |
float fa[11], *afp[17]; // fa est un tableau de 11 floats // afp est un tableau de 17 pointeurs vers des floats
Explication
Il existe plusieurs variantes de types de tableaux : les tableaux de taille constante connue, les tableaux de longueur variable et les tableaux de taille inconnue.
Tableaux de taille constante connue
Si expression dans un déclarateur de tableau est une expression constante entière avec une valeur supérieure à zéro et que le type d'élément est un type avec une taille constante connue (c'est-à-dire que les éléments ne sont pas VLA) (depuis C99) , alors le déclarateur déclare un tableau de taille constante connue :
int n[10]; // les constantes entières sont des expressions constantes char o[sizeof(double)]; // sizeof est une expression constante enum { MAX_SZ=100 }; int n[MAX_SZ]; // les constantes enum sont des expressions constantes
Les tableaux de taille constante connue peuvent utiliser des initialiseurs de tableau pour fournir leurs valeurs initiales :
int a[5] = {1,2,3}; // déclare int[5] initialisé à 1,2,3,0,0 char str[] = "abc"; // déclare char[4] initialisé à 'a','b','c','\0'
|
Dans les listes de paramètres de fonction, des éléments syntaxiques supplémentaires sont autorisés dans les déclarateurs de tableau : le mot-clé
Dans chaque
appel de fonction
à une fonction où un paramètre de tableau utilise le mot-clé
void fadd(double a[static 10], const double b[static 10]) { for (int i = 0; i < 10; i++) { if (a[i] < 0.0) return; a[i] += b[i]; } } // un appel à fadd peut effectuer une vérification des limites à la compilation // et permet également des optimisations telles que le préchargement de 10 doubles int main(void) { double a[10] = {0}, b[20] = {0}; fadd(a, b); // OK double x[5] = {0}; fadd(x, b); // comportement indéfini : l'argument tableau est trop petit } Si qualifiers sont présents, ils qualifient le type pointeur vers lequel le type paramètre tableau est transformé : int f(const int a[20]) { // dans cette fonction, a a le type const int* (pointeur vers const int) } int g(const int a[const 20]) { // dans cette fonction, a a le type const int* const (pointeur const vers const int) }
Ceci est couramment utilisé avec le qualificateur de type
void fadd(double a[static restrict 10], const double b[static restrict 10]) { for (int i = 0; i < 10; i++) // la boucle peut être déroulée et réorganisée { if (a[i] < 0.0) break; a[i] += b[i]; } } Tableaux de longueur variableSi expression n'est pas une expression constante entière , le déclarateur est pour un tableau de taille variable. Chaque fois que le flux de contrôle passe par-dessus la déclaration, expression est évaluée (et elle doit toujours s'évaluer à une valeur supérieure à zéro), et le tableau est alloué (corrélativement, lifetime d'un VLA se termine lorsque la déclaration sort de la portée). La taille de chaque instance VLA ne change pas pendant sa durée de vie, mais lors d'un autre passage sur le même code, elle peut être allouée avec une taille différente.
Exécuter ce code
#include <stdio.h> int main(void) { int n = 1; label:; int a[n]; // réalloué 10 fois, chaque fois avec une taille différente printf("Le tableau a %zu éléments\n", sizeof a / sizeof *a); if (n++ < 10) goto label; // quitter la portée d'un VLA met fin à sa durée de vie }
Si la taille est
Les tableaux de longueur variable et les types dérivés de ceux-ci (pointeurs vers eux, etc.) sont communément appelés "types à modification variable" (VM). Les objets de tout type à modification variable ne peuvent être déclarés qu'au niveau de la portée de bloc ou de la portée de prototype de fonction. extern int n; int A[n]; // Erreur : VLA de portée fichier extern int (*p2)[n]; // Erreur : VM de portée fichier int B[100]; // OK : tableau de portée fichier avec taille constante connue void fvla(int m, int C[m][m]); // OK : VLA de portée prototype Les VLA doivent avoir une durée de stockage automatique ou allouée. Les pointeurs vers des VLA, mais pas les VLA eux-mêmes, peuvent également avoir une durée de stockage statique. Aucun type VM ne peut avoir de liaison. void fvla(int m, int C[m][m]) // OK : pointeur de durée automatique/portée de bloc vers VLA { typedef int VLA[m][m]; // OK : VLA de portée de bloc int D[m]; // OK : VLA de durée automatique/portée de bloc // static int E[m]; // Erreur : VLA de durée statique // extern int F[m]; // Erreur : VLA avec liaison int (*s)[m]; // OK : VM de durée automatique/portée de bloc s = malloc(m * sizeof(int)); // OK : s pointe vers VLA dans le stockage alloué // extern int (*r)[m]; // Erreur : VM avec liaison static int (*q)[m] = &B; // OK : VM de durée statique/portée de bloc} } Les types à modification variable ne peuvent pas être membres de structs ou d'unions. struct tag { int z[n]; // Erreur : membre de structure VLA int (*y)[n]; // Erreur : membre de structure VM }; |
(depuis C99) |
|
Si le compilateur définit la constante macro __STDC_NO_VLA__ comme la constante entière 1 , alors les types VLA et VM ne sont pas pris en charge. |
(depuis C11)
(jusqu'à C23) |
|
Si le compilateur définit la constante macro __STDC_NO_VLA__ comme la constante entière 1 , alors les objets VLA avec durée de stockage automatique ne sont pas pris en charge. La prise en charge des types VM et des VLA avec durées de stockage allouées est obligatoire. |
(depuis C23) |
Tableaux de taille inconnue
Si
l'expression
dans un déclarateur de tableau est omise, elle déclare un tableau de taille inconnue. Sauf dans les listes de paramètres de fonction (où de tels tableaux sont transformés en pointeurs) et lorsqu'un
initialiseur
est disponible, ce type est un
type incomplet
(notez qu'un VLA de taille non spécifiée, déclaré avec
*
comme taille, est un type complet)
(depuis C99)
:
extern int x[]; // le type de x est "tableau de taille inconnue d'entiers" int a[] = {1,2,3}; // le type de a est "tableau de 3 entiers"
|
Dans une définition de struct , un tableau de taille inconnue peut apparaître comme dernier membre (à condition qu'il y ait au moins un autre membre nommé), auquel cas il s'agit d'un cas spécial connu sous le nom de flexible array member . Voir struct pour plus de détails : struct s { int n; double d[]; }; // s.d is a flexible array member struct s *s1 = malloc(sizeof (struct s) + (sizeof (double) * 8)); // as if d was double d[8]
|
(depuis C99) |
Qualificateurs
|
Si un type tableau est déclaré avec un qualificatif
|
(jusqu'à C23) |
|
Un type tableau et son type d'élément sont toujours considérés comme identiquement qualifiés, sauf qu'un type tableau n'est jamais considéré comme qualifié
|
(depuis C23) |
typedef int A[2][3]; const A a = {{4, 5, 6}, {7, 8, 9}}; // tableau de tableau de const int int* pi = a[0]; // Erreur : a[0] a le type const int* void* unqual_ptr = a; // OK jusqu'à C23 ; erreur depuis C23 // Notes : clang applique la règle en C++/C23 même dans les modes C89-C17
|
typedef int A[2]; // _Atomic A a0 = {0}; // Erreur // _Atomic(A) a1 = {0}; // Erreur _Atomic int a2[2] = {0}; // OK _Atomic(int) a3[2] = {0}; // OK |
(depuis C11) |
Affectation
Les objets de type tableau ne sont pas des lvalues modifiables , et bien que leur adresse puisse être prise, ils ne peuvent pas apparaître à gauche d'un opérateur d'affectation. Cependant, les structures avec des membres tableau sont des lvalues modifiables et peuvent être assignées :
int a[3] = {1,2,3}, b[3] = {4,5,6}; int (*p)[3] = &a; // correct, l'adresse de a peut être prise // a = b; // erreur, a est un tableau struct { int c[3]; } s1, s2 = {3,4,5}; s1 = s2; // correct : on peut assigner des structures contenant des membres tableau
Conversion de tableau en pointeur
Toute expression lvalue de type tableau, lorsqu'elle est utilisée dans tout contexte autre que
- en tant qu'opérande de l' opérateur d'adressage
-
en tant qu'opérande de
sizeof -
en tant qu'opérande de
typeofettypeof_unqual(depuis C23) - en tant que littéral de chaîne utilisé pour l' initialisation de tableau
| (depuis C11) |
subit une conversion implicite vers le pointeur vers son premier élément. Le résultat n'est pas un lvalue.
Si le tableau a été déclaré
register
, le comportement du programme qui tente une telle conversion est indéfini.
Lorsqu'un type tableau est utilisé dans une liste de paramètres de fonction, il est transformé en type pointeur correspondant : int f ( int a [ 2 ] ) et int f ( int * a ) déclarent la même fonction. Comme le type réel du paramètre de fonction est un type pointeur, un appel de fonction avec un argument tableau effectue une conversion tableau-vers-pointeur ; la taille du tableau argument n'est pas disponible pour la fonction appelée et doit être transmise explicitement :
#include <stdio.h> void f(int a[], int sz) // déclare en réalité void f(int* a, int sz) { for (int i = 0; i < sz; ++i) printf("%d\n", a[i]); } void g(int (*a)[10]) // le paramètre pointeur vers tableau n'est pas transformé { for (int i = 0; i < 10; ++i) printf("%d\n", (*a)[i]); } int main(void) { int a[10] = {0}; f(a, 10); // convertit a en int*, passe le pointeur g(&a); // passe un pointeur vers le tableau (pas besoin de passer la taille) }
Tableaux multidimensionnels
Lorsque le type d'élément d'un tableau est un autre tableau, on dit que le tableau est multidimensionnel :
// tableau de 2 tableaux de 3 entiers chacun int a[2][3] = {{1,2,3}, // peut être vu comme une matrice 2x3 {4,5,6}}; // avec un agencement row-major
Notez que lorsque la conversion tableau-vers-pointeur est appliquée, un tableau multidimensionnel est converti en un pointeur vers son premier élément, par exemple, un pointeur vers la première ligne :
int a[2][3]; // matrice 2x3 int (*p1)[3] = a; // pointeur vers la première ligne de 3 éléments int b[3][3][3]; // cube 3x3x3 int (*p2)[3][3] = b; // pointeur vers le premier plan 3x3
|
Les tableaux multidimensionnels peuvent être modifiés variablement dans chaque dimension si les VLA sont supportés (depuis C11) : int n = 10; int a[n][2*n]; |
(depuis C99) |
Notes
Les déclarations de tableaux de longueur zéro ne sont pas autorisées, même si certains compilateurs les proposent en tant qu'extensions (généralement comme une implémentation pré-C99 des flexible array members ).
Si l'expression de taille expression d'un VLA a des effets de bord, ils sont garantis d'être produits sauf lorsqu'elle fait partie d'une expression sizeof dont le résultat n'en dépend pas :
int n = 5, m = 5; size_t sz = sizeof(int (*[n++])[m++]); // n est incrémenté, m peut ou non être incrémenté
Références
- Norme C23 (ISO/IEC 9899:2024):
-
- 6.7.6.2 Déclarateurs de tableau (p: TBD)
- Norme C17 (ISO/CEI 9899:2018) :
-
- 6.7.6.2 Déclarateurs de tableau (p: 94-96)
- Norme C11 (ISO/CEI 9899:2011) :
-
- 6.7.6.2 Déclarateurs de tableau (p: 130-132)
- Norme C99 (ISO/CEI 9899:1999) :
-
- 6.7.5.2 Déclarateurs de tableau (p: 116-118)
- Norme C89/C90 (ISO/IEC 9899:1990) :
-
- 3.5.4.2 Déclarateurs de tableau
Voir aussi
|
Documentation C++
pour
Déclaration de tableau
|