Initialisation des tableaux et des structures en C

J’ai écrit un précédent article sur les designated initializers en C99. Je terminais cet article par une question : qu’advient-il des champs non désignés ? Quelle valeur ont-ils ? Il était temps de répondre à cette question et j’ai donc jeté un coup œil à la norme. Comme d’habitude, un coup d’œil se transforme en jeu de piste. Le cas particulier des designated initializers se résout en inspectant les règles d’initialisation des tableaux et des structures en général. Extraits choisis.

Commençons par faire simple

Il y a quelques règles assez connues en C pour l’initialisation des tableaux et des structures.

Commençons par l’état des variables non initialisées explicitement :

  • Les variables utilisant une allocation de type statique (c’est le cas des variables globales et des variables statiques) sont toujours initialisées à zéro de manière implicite.
  • Les variables avec une allocation automatique (celles déclarées sur la pile, dont la majorité des variables) sont laissées à une valeur indéfinie, qui dépend de l’état de la mémoire à leur création.

Ces deux règles s’appliquent d’ailleurs à tous les types de variables.

Les tableaux et les structures s’initialisent explicitement avec une liste de valeurs placée entre accolades et on sait que :

  • Les champs d’une structure doivent être donnés dans l’ordre (sauf si designated initializers).
  • Les valeurs remplissent les cases d’un tableau dans l’ordre à partir de l’indice 0 (même remarque).
  • Un tableau dont on ne précise pas la taille aura la même longueur que la liste d’initialisation, qui est obligatoire dans ce cas.
  • Si la liste d’initialisation ne contient pas autant de champs que la structure et ne contient pas de designated initializer, le compilateur émet généralement un warning: missing initializer.
  • Si la liste d’initialisation ne contient pas assez d’éléments pour initialiser toutes les cases d’un tableau, alors les cases restantes sont implicitement initialisées à zéro.
  • L’utilisation d’une liste entre accolades est possible uniquement à l’initialisation et est impossible lors d’une affectation ultérieure.

Voici un code regroupant ces différents exemples :

struct s
{
    int a;
    int b;
    int c;
    int d;
};

struct s sg;    // contenu vaut zero
int tg[5];      // idem

int main(void)
{
    struct s s1 = {12, .c=17};      // pas de warning
    struct s s2 = {12, 15};         // warning : missing initializer
    struct s s3 = {12, 17, 19, 21}; // pas de warning

    int t1[5] = {1,2,3};            // les deux derniers elements valent 0
    int t2[ ] = {1,2,3};            // t2 a une longueur de 3

    struct s s4;                    // contenu indefini
    int t3[5];                      // idem

//    int t4[];                     // Genere : "error: array size missing in 't4'"

    return s2.c + s2.d;             // --> return 0
}

Pour être rigoureux et tout expliquer

Explorons la norme pour vérifier tout ça.

Les tableaux et les structures sont désignés de manière commune par le terme de « type agrégés », comme indiqué au point 21 de la partie 6.2.5 Types :

Arithmetic types and pointer types are collectively called scalar types. Array
and structure types are collectively called aggregate types.37)

La note de bas de page 37 précise au passage qu’une union ne peut pas être considérée comme un type agrégé car :

37) Note that aggregate type does not include union type because an object
with union type can only contain one member at a time.

Allons maintenant à la partie 6.7.8 Initialization. On lit avec intérêt le point 10 :

If an object that has automatic storage duration is not initialized explicitly, its value is
indeterminate. If an object that has static storage duration is not initialized explicitly,
then:
— if it has pointer type, it is initialized to a null pointer;
— if it has arithmetic type, it is initialized to (positive or unsigned) zero;
— if it is an aggregate, every member is initialized (recursively) according to these rules;
— if it is a union, the first named member is initialized (recursively) according to these
rules.

On retrouve ici la règle que j’énonçais un peu plus haut : les variables globales et statiques sont initialisées implicitement à zéro (ou NULL) ; les autres contiennent tout et n’importe quoi.

A ce moment là, on se dit que c’est étonnant quand même d’avoir la fin de notre tableau initialisé à zéro et que notre programme renvoie 0 et non une valeur indéterminée. Ces éléments utilisent une classe d’allocation statique et ne sont pas initialisés explicitement : cette règle suppose que leur valeur est indéterminée. C’était sans compter sur le point 21 :

If there are fewer initializers in a brace-enclosed list than there are elements or members
of an aggregate, or fewer characters in a string literal used to initialize an array of known
size than there are elements in the array, the remainder of the aggregate shall be
initialized implicitly the same as objects that have static storage duration.

Si les éléments entre accolades ne sont pas assez nombreux pour remplir tout le type agrégé (champs d’une structure, cases d’un tableau) alors les champs restants sont initialisés comme si les variables étaient statiques et sont donc à NULL……..ou zéro ! CQFD.

C’est valable pour les structures (retour du programme ci-dessus) comme pour les tableaux, pour lesquels la norme donne le bel exemple suivant (point 26) :

EXAMPLE 3 The declaration

int y[4][3] = {
    { 1, 3, 5 },
    { 2, 4, 6 },
    { 3, 5, 7 },
};

is a definition with a fully bracketed initialization: 1, 3, and 5 initialize the first row of y (the array object y[0]), namely y[0][0], y[0][1], and y[0][2]. Likewise the next two lines initialize y[1] and y[2]. The initializer ends early, so y[3] is initialized with zeros.

Cette règle s’applique donc  aussi dans le cas des designated initializers, comme montré dans les points 36 et 37 :

36 EXAMPLE 12 Space can be ‘‘allocated’’ from both ends of an array by using a single designator:

int a[MAX] = {
    1, 3, 5, 7, 9, [MAX-5] = 8, 6, 4, 2, 0
};

37 In the above, if MAX is greater than ten, there will be some zero-valued elements in the middle; if it is less than ten, some of the values provided by the first five initializers will be overridden by the second five.

Pour finir, la confirmation que la longueur des tableaux, si elle n’est pas spécifiée, est donnée par la taille de la liste :

25 EXAMPLE 2 The declaration
int x[] = { 1, 3, 5 };
defines and initializes x as a one-dimensional array object that has three elements, as no size was specified
and there are three initializers.

La norme a encore frappé !

PS : Pour ceux d’entre vous qui n’auraient pas la norme sur votre bureau ou votre table de chevet et qui souhaiterait lire ce magnifique document, vous trouverez le PDF ici.

 

Publicités

3 Réponses

  1. Manu

    Fait gaffe, on va commencer à croire que tu es fétichiste de la norme C99 ! 🙂

    J'aime

    11 septembre 2012 à 11:37

  2. Pingback: struct : déclarer des types et des variables en C | Pierre Gradot

Laisser un commentaire

Entrez vos coordonnées ci-dessous ou cliquez sur une icône pour vous connecter:

Logo WordPress.com

Vous commentez à l'aide de votre compte WordPress.com. Déconnexion / Changer )

Image Twitter

Vous commentez à l'aide de votre compte Twitter. Déconnexion / Changer )

Photo Facebook

Vous commentez à l'aide de votre compte Facebook. Déconnexion / Changer )

Photo Google+

Vous commentez à l'aide de votre compte Google+. Déconnexion / Changer )

Connexion à %s