Struct vs littéraux de chaîne? Lecture seule vs lecture-écriture?

La norme C99 autorise-t-elle l’écriture dans des littéraux composés (structs)? Il semble que cela ne fournit pas d’écriture sur des chaînes littérales. Je pose la question à ce sujet car il est écrit dans Programmation C: une approche moderne, 2e édition , page 406.

Q. Autoriser un pointeur sur un littéral composé semblerait permettre de modifier le littéral. Est-ce le cas?

R. Oui. Les littéraux composés sont des valeurs pouvant être modifiées.

Mais je ne comprends pas très bien comment cela fonctionne et comment cela fonctionne avec des littéraux de chaîne que vous ne pouvez certainement pas modifier.

char *foo = "foo bar"; struct bar { char *a; int g; }; struct bar *baz = &(struct bar){.a = "foo bar", .g = 5}; int main () { // Segfaults // (baz->a)[0] = 'X'; // printf( "%s", baz->a ); // Segfaults // foo[0] = 'a'; // printf("%s", foo); baz->g = 9; printf("%d", baz->g); return 0; } 

Vous pouvez voir sur ma liste de choses que segfault, écrire dans baz->a provoque un segfault. Mais écrire à baz->g ne le fait pas. Pourquoi est-ce que l’un d’eux causerait un segfault et pas l’autre? En quoi les littéraux de structure sont-ils différents des littéraux de chaîne? Pourquoi les littéraux de structure ne seraient-ils pas également placés dans une section de mémoire en lecture seule et le comportement est-il défini ou indéfini pour les deux (question sur les normes)?

Tout d’abord, votre littéral struct a un membre de pointeur initialisé sur un littéral chaîne. Les membres de la structure elle-même sont accessibles en écriture, y compris le membre du pointeur. Seul le contenu du littéral de chaîne n’est pas accessible en écriture.

Les littéraux de chaîne font partie du langage depuis le début, alors que les littéraux de structure (officiellement appelés littéraux composés ) constituent un ajout relativement récent, à partir de C99. À ce moment-là, de nombreuses implémentations existaient pour placer les littéraux de chaîne dans la mémoire en lecture seule, en particulier sur les systèmes embarqués dotés d’une quantité infime de RAM. À ce moment-là, les concepteurs de la norme avaient le choix d’exiger que les littéraux de chaîne soient déplacés vers un emplacement inscriptible, ce qui permettait aux littéraux de structure d’être en lecture seule ou de laisser les choses telles quelles. Aucune des trois solutions n’étant idéale, il semblerait qu’elles aient pris le chemin de la moindre résistance et aient tout laissé tel quel.

La norme C99 autorise-t-elle l’écriture dans des littéraux composés (structs)?

La norme C99 n’interdit pas explicitement l’écriture dans des objects de données initialisés avec des littéraux composés. Cela diffère des littéraux de chaîne, dont la modification est considérée comme un comportement non défini par la norme.

La norme définit essentiellement les mêmes caractéristiques pour les littéraux de chaîne et pour les littéraux de composition avec un type qualifié de const utilisé en dehors du corps d’une fonction.

Durée de vie

  • Littéraux de chaîne : toujours statique.

    §6.4.5p6 Dans la phase de traduction 7, un octet ou un code de valeur zéro est ajouté à chaque séquence de caractères multi-octets résultant d’un littéral de chaîne ou de littéraux. La séquence de caractères multi-octets est ensuite utilisée pour initialiser un tableau de durée et de longueur de stockage statique juste suffisante pour contenir la séquence.

  • Littéraux composés : Automatique si utilisé dans un corps de fonction, sinon statique.

    §6.5.2.5p5 La valeur du littéral composé est celle d’un object sans nom initialisé par la liste des initialiseurs. Si le littéral composé survient en dehors du corps d’une fonction, l’object a une durée de stockage statique. sinon, la durée de stockage automatique est associée au bloc englobant.

Éventuellement partagé

  • Les littéraux de chaîne et les littéraux composés qualifiés de const peuvent être partagés. Vous devriez être préparé à cette éventualité, mais vous ne pouvez pas vous y fier.

§6.4.5p7 Il n’est pas spécifié si [les tableaux créés pour les littéraux de chaîne] sont distincts à condition que leurs éléments aient les valeurs appropriées.

§6.5.2.5p7 Les littéraux de chaîne et les littéraux composés avec des types qualifiés de const ne doivent pas nécessairement désigner des objects distincts.

Mutabilité

  • La modification d’un littéral de chaîne ou d’un littéral composé qualifié de const constitue un comportement indéfini. En effet, tenter de modifier un object qualifié de const est un comportement indéfini, bien que la formulation de la norme soit probablement sujette à la division des cheveux.

§6.4.5p7 Si le programme tente de modifier [le tableau contenant un littéral de chaîne], le comportement est indéfini.

§6.7.3p6 Si l’on tente de modifier un object défini avec un type qualifié de const en utilisant une valeur lvalue avec un type non qualifié de const, le comportement est indéfini.

  • Un littéral composé non qualifié de const peut être librement modifié. Je n’ai pas de devis pour cela, mais le fait que la modification ne soit pas explicitement interdite me semble être définitif. Il n’est pas nécessaire de dire explicitement que les objects mutables peuvent être mutés.

Le fait que la durée de vie des littéraux composés à l’intérieur des corps de fonction soit automatique peut conduire à des bogues subtils:

 /* This is fine */ const char* foo(void) { return "abcde"; } /* This is not OK */ const int* oops(void) { return (const int[]){1, 2, 3, 4, 5}; ; 

La norme C99 autorise-t-elle l’écriture dans des littéraux composés (structs)?

En écrivant au littéral composé, si vous voulez dire modifier des éléments d’un littéral composé, alors oui, il le fait s’il ne s’agit pas d’un littéral composé à lecture seule.

C99-6.5.2.5:

Si le nom du type spécifie un tableau de taille inconnue, la taille est déterminée par la liste d’initialisation spécifiée en 6.7.8 et le type du littéral composé est celui du type de tableau terminé . Sinon (lorsque le nom du type spécifie un type d’object), le type du littéral composé est celui spécifié par le nom du type. Dans les deux cas, le résultat est une valeur .

Cela signifie que les littéraux composés sont des lvalues comme des tableaux et que les éléments d’un littéral composé peuvent être modifiés, tout comme vous pouvez modifier un type d’agrégat. Par exemple

 // 1 ((int []) {1,2,3})[0] = 100; // OK // 2 (char[]){"Hello World"}[0] = 'Y'; // OK. This is not a ssortingng literal! // 3 char* str = (char[]){"Hello World"}; *str = 'Y'; // OK. Writing to a compound literal via pointer. // 4 (const float []){1e0, 1e1, 1e2}[0] = 1e7 // ERROR. Read only compound literal 

Dans votre code, vous essayez de modifier un élément littéral composé qui pointe vers un littéral chaîne non modifiable. Si cet élément est initialisé avec un littéral composé, il peut être modifié.

 struct bar *baz = &(struct bar){.a = (char[]){"foo bar"}, .g = 5}; 

Cet extrait fonctionnera maintenant

 Segfaults (baz->a)[0] = 'X'; printf( "%s", baz->a ); 

Un autre standard donne également un exemple, dans la même section mentionnée ci-dessus, et distingue un littéral de chaîne, un littéral composé et un littéral composé en lecture seule:

13 EXEMPLE 5 Les trois expressions suivantes ont des significations différentes:

 "/tmp/fileXXXXXX" (char []){"/tmp/fileXXXXXX"} (const char []){"/tmp/fileXXXXXX"} 

Le premier a toujours une durée de stockage statique et un type array de char, mais ne doit pas nécessairement être modifiable; les deux derniers ont une durée de stockage automatique lorsqu’ils se produisent dans le corps d’une fonction, et le premier de ces deux est modifiable .