La norme C99 dit dans 6.5.16: 2:
Un opérateur d'assignation doit avoir une valeur l modifiable comme opérande gauche.
et en 6.3.2.1:1:
Une lvalue modifiable est une lvalue qui n'a pas de type tableau, qui n'a pas de type incomplet, qui n'a pas de type qualifié const et qui, s'il s'agit d'une structure ou d'union, n'a pas de membre (y compris, récursivement, tout membre ou élément de tous les agrégats ou unions contenus) avec un type qualifié const.
Maintenant, considérons un non const
struct
avec un const
champ.
typedef struct S_s {
const int _a;
} S_t;
Par défaut, le code suivant est un comportement non défini (UB):
S_t s1;
S_t s2 = { ._a = 2 };
s1 = s2;
Le problème sémantique avec cela est que l'entité englobante ( struct
) doit être considérée comme inscriptible (non en lecture seule), à en juger par le type déclaré de l'entité ( S_t s1
), mais ne doit pas être considérée comme inscriptible par le libellé de la norme (les 2 clauses en haut) à cause du const
champ _a
. La norme ne rend pas clair pour un programmeur lisant le code que l'affectation est en fait un UB, car il est impossible de dire cela sans la définition du struct S_s ... S_t
type.
De plus, l'accès en lecture seule au champ n'est de toute façon imposé que syntaxiquement. Il n'y a aucun moyen que certains const
champs de non const
struct
soient vraiment placés dans un stockage en lecture seule. Mais une telle formulation de la norme proscrit le code qui rejette délibérément le const
qualificatif des champs dans les procédures d'accès de ces champs, comme ça ( Est-ce une bonne idée de const-qualifier les champs de structure en C? ):
(*)
#include <stdlib.h>
#include <stdio.h>
typedef struct S_s {
const int _a;
} S_t;
S_t *
create_S(void) {
return calloc(sizeof(S_t), 1);
}
void
destroy_S(S_t *s) {
free(s);
}
const int
get_S_a(const S_t *s) {
return s->_a;
}
void
set_S_a(S_t *s, const int a) {
int *a_p = (int *)&s->_a;
*a_p = a;
}
int
main(void) {
S_t s1;
// s1._a = 5; // Error
set_S_a(&s1, 5); // OK
S_t *s2 = create_S();
// s2->_a = 8; // Error
set_S_a(s2, 8); // OK
printf("s1.a == %d\n", get_S_a(&s1));
printf("s2->a == %d\n", get_S_a(s2));
destroy_S(s2);
}
Donc, pour une raison quelconque, pour qu'un ensemble struct
soit en lecture seule, il suffit de le déclarerconst
const S_t s3;
Mais pour qu'un ensemble ne struct
soit pas en lecture seule, il ne suffit pas de le déclarer sans const
.
Ce que je pense serait mieux, c'est:
- Contraindre la création de non-
const
structures avec desconst
champs et émettre un diagnostic dans un tel cas. Cela indiquerait clairement que lesstruct
champs en lecture seule contenant sont eux-mêmes en lecture seule. - Définir le comportement en cas d'écriture dans un
const
champ appartenant à une non-const
structure comme rendre le code ci-dessus (*) conforme au Standard.
Sinon, le comportement n'est pas cohérent et difficile à comprendre.
Alors, quelle est la raison pour laquelle C Standard considère const
-ness récursivement, comme il le dit?
Réponses:
Du seul point de vue du type, ne pas le faire serait fâcheux (en d'autres termes: terriblement brisé et intentionnellement peu fiable).
Et c'est à cause de ce que "=" signifie sur une structure: c'est une affectation récursive. Il s'ensuit que vous avez finalement un
s1._a = <value>
événement "à l'intérieur des règles de frappe". Si la norme le permet pour lesconst
champs "imbriqués" , son ajout d'une grave incohérence dans sa définition de système de type comme une contradiction explicite (pourrait aussi bien jeter laconst
fonctionnalité, car elle est devenue inutile et peu fiable par sa définition même).Pour autant que je sache, votre solution (1) oblige inutilement toute la structure à se trouver à
const
chaque fois que l'un de ses domaines l'estconst
. De cette façon,s1._b = b
serait illégal pour un._b
champ non-const sur un non-consts1
contenant aconst a
.la source
C
a à peine un système de type sonore (plus comme un tas de cas d'angle attachés les uns aux autres au fil des ans). En outre, l'autre façon de mettre l'affectation à unstruct
estmemcpy(s_dest, s_src, sizeof(S_t))
. Et je suis presque sûr que c'est la façon dont il est mis en œuvre. Et dans ce cas, même le "système de type" existant ne vous interdit pas de le faire.La raison en est que les champs en lecture seule sont en lecture seule. Pas de grosse surprise là-bas.
Vous supposez à tort que le seul effet est sur le placement dans la ROM, ce qui est en effet impossible lorsqu'il y a des champs non const adjacents. En réalité, les optimiseurs peuvent supposer que les
const
expressions ne sont pas écrites et optimiser en fonction de cela. Bien sûr, cette hypothèse ne tient pas lorsqu'il existe des alias non const.Votre solution (1) casse le code légal et raisonnable existant. Cela n'arrivera pas. Votre solution (2) supprime à peu près le sens de
const
sur les membres. Bien que cela ne brise pas le code existant, il semble manquer de justification.la source
const
champs ne sont pas écrits, car vous pouvez toujours utilisermemset
oumemcpy
, et cela serait même conforme à la norme. (1) peut être implémenté comme, à tout le moins, un avertissement supplémentaire, activé par un indicateur. La justification de (2) est que, eh bien, exactement - il n'y a aucun moyen qu'un composant destruct
puisse être considéré comme non inscriptible lorsque la structure entière est inscriptible.operator=
en termes de membres, et donc ne définit pas unoperator=
quand un membre l'estconst
. C et C ++ sont toujours compatibles ici.memcpy
? Comme pour d'autres raisons - ok, c'est un héritage, mais pourquoi a-t-il été fait de cette manière en premier lieu?memcpy
est juste. La citation d'AFACIT John Bode dans votre autre question est juste: votre code écrit dans un objet qualifié const et n'est donc PAS une plainte standard, fin de la discussion.