Quelle est la raison pour laquelle le standard C considère la constance récursivement?


9

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 structavec un constchamp.

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 constchamp _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_ttype.

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 constchamps de non const structsoient 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 constqualificatif 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 structsoit en lecture seule, il suffit de le déclarerconst

const S_t s3;

Mais pour qu'un ensemble ne structsoit pas en lecture seule, il ne suffit pas de le déclarer sans const.

Ce que je pense serait mieux, c'est:

  1. Contraindre la création de non- conststructures avec des constchamps et émettre un diagnostic dans un tel cas. Cela indiquerait clairement que les structchamps en lecture seule contenant sont eux-mêmes en lecture seule.
  2. Définir le comportement en cas d'écriture dans un constchamp appartenant à une non- conststructure 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?


Pour être honnête, je ne vois pas de question là-dedans.
Bart van Ingen Schenau

@BartvanIngenSchenau modifié pour ajouter la question posée dans le sujet à la fin du corps
Michael Pankov

1
Pourquoi le downvote?
Michael Pankov

Réponses:


4

Alors, quelle est la raison pour laquelle C Standard considère la constance récursivement, comme elle le dit?

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 les constchamps "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 la constfonctionnalité, 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 à constchaque fois que l'un de ses domaines l'est const. De cette façon, s1._b = bserait illégal pour un ._bchamp non-const sur un non-const s1contenant a const a.


Bien. Ca à 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 à un structest memcpy(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.
Michael Pankov

2
Très vrai. J'espère que je n'ai pas laissé entendre que le système de type de C est solide, mais seulement que rendre délibérément une sémantique malsaine la détruit délibérément. De plus, bien que le système de type de C ne soit pas fortement appliqué, les moyens de le briser sont souvent explicites (pointeurs, accès indirect, transtypages) - même si ses effets sont souvent implicites et difficiles à suivre. Ainsi, le fait d'avoir des «barrières» explicites pour les enfreindre est plus utile que d'avoir une contradiction dans les définitions elles-mêmes.
Thiago Silva

2

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 constexpressions 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 constsur les membres. Bien que cela ne brise pas le code existant, il semble manquer de justification.


Je suis sûr à 90% que les optimiseurs ne peuvent pas supposer que les constchamps ne sont pas écrits, car vous pouvez toujours utiliser memsetou memcpy, 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 de structpuisse être considéré comme non inscriptible lorsque la structure entière est inscriptible.
Michael Pankov

Un "diagnostic facultatif déterminé par un indicateur" serait une exigence unique que la norme exigerait. De plus, la définition du drapeau casserait toujours le code existant, donc en fait personne ne s'embêterait avec le drapeau et ce serait une impasse. Comme pour (2), 6.3.2.1:1 spécifie exactement le contraire: la structure entière est non inscriptible chaque fois qu'un composant l'est. Cependant, d' autres composants peuvent toujours être accessibles en écriture. Cf. C ++ qui définit également operator=en termes de membres, et donc ne définit pas un operator=quand un membre l'est const. C et C ++ sont toujours compatibles ici.
MSalters

@constantius - Le fait que vous POUVEZ faire quelque chose pour contourner délibérément la constance d'un membre n'est PAS une raison pour l'optimiseur d'ignorer cette constance. Vous POUVEZ éliminer la constance à l'intérieur d'une fonction, vous permettant de changer des choses. Mais l'optimiseur dans le contexte d'appel est toujours autorisé à supposer que vous ne le ferez pas. Constness est utile pour le programmeur, mais c'est aussi un don de Dieu pour l'optimiseur dans certains cas.
Michael Kohne

Alors pourquoi une structure non inscriptible peut-elle être écrasée avec ie 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?
Michael Pankov

1
Je me demande toujours si votre commentaire memcpyest 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.
MSalters
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.