Namespaces
Variants

Array declaration

From cppreference.net

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)
1,2) Syntaxe générale du déclarateur de tableau
3) Déclarateur pour VLA de taille non spécifiée (peut apparaître uniquement dans la portée de prototype de fonction) où
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é static et les qualifiers , qui peuvent apparaître dans n'importe quel ordre avant l'expression de taille (ils peuvent également apparaître même lorsque l'expression de taille est omise).

Dans chaque appel de fonction à une fonction où un paramètre de tableau utilise le mot-clé static entre [ et ] , la valeur du paramètre effectif doit être un pointeur valide vers le premier élément d'un tableau avec au moins autant d'éléments que spécifié par expression :

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 restrict :

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 variable

Si 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.

#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 * , la déclaration est pour un VLA de taille non spécifiée. Une telle déclaration ne peut apparaître que dans une portée de prototype de fonction, et déclare un tableau de type complet. En fait, tous les déclarateurs VLA dans la portée de prototype de fonction sont traités comme si expression était remplacée par * .

void foo(size_t x, int a[*]);
void foo(size_t x, int a[x])
{
    printf("%zu\n", sizeof a); // identique à sizeof(int*)
}

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 const , volatile , ou restrict (depuis C99) (ce qui est possible via l'utilisation de typedef ), le type tableau n'est pas qualifié, mais son type d'élément l'est :

(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é _Atomic .

(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

_Atomic ne peut pas être appliqué à un type tableau, bien qu'un tableau de type atomique soit autorisé.

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

(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.

int a[3] = {1,2,3};
int* p = a;
printf("%zu\n", sizeof a); // affiche la taille du tableau
printf("%zu\n", sizeof p); // affiche la taille d'un pointeur

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