La const-casting via un comportement non défini d’union?

Contrairement à C ++, C n’a aucune notion de const_cast . Autrement dit, il n’existe aucun moyen valide de convertir un pointeur qualifié de const en un pointeur non qualifié:

 void const * p; void * q = p; // not good 

Tout d’abord: ce comportement est-il réellement indéfini?

En tout état de cause, GCC le met en garde. Pour créer un code “propre” qui nécessite une conversion constante (c’est-à-dire où je peux garantir que je ne muterai pas le contenu, mais que j’ai un pointeur mutable), j’ai vu le tour de “conversion” suivant:

 typedef union constcaster_ { void * mp; void const * cp; } constcaster; 

Utilisation: u.cp = p; q = u.mp; u.cp = p; q = u.mp; .

Quelles sont les règles du langage C pour éliminer la constance par une telle union? Ma connaissance de C n’est que très inégale, mais j’ai entendu dire que C est beaucoup plus indulgent sur l’access aux syndicats que C ++. Par conséquent, même si j’ai un mauvais pressentiment à propos de cette construction, je voudrais un argument de la norme mais si cela a changé en C11, il sera bon de le savoir).

    Sa mise en œuvre est définie, voir C99 6.5.2.3/5:

    Si la valeur d’un membre d’un object d’union est utilisée lorsque le magasin le plus récent de l’object appartenait à un autre membre, le comportement est défini par l’implémentation.

    Mise à jour: @AaronMcDaid a déclaré que cela pourrait être bien défini, après tout.

    La norme spécifiait les 6.2.5 / 27 suivants:

    De même, les pointeurs sur les versions qualifiées ou non qualifiées de types compatibles doivent avoir les mêmes exigences de représentation et d’alignement.27)

    27) Les mêmes exigences de représentation et d’alignement impliquent l’interchangeabilité en tant qu’arguments de fonctions, renvoient des valeurs de fonctions et des membres d’unions.

    Et (6.7.2.1/14):

    Un pointeur sur un object d’union, converti de manière appropriée, pointe sur chacun de ses membres (ou si un membre est un champ de bits, puis sur l’unité dans laquelle il réside), et inversement.

    On pourrait en conclure que, dans ce cas particulier , il n’ya plus de place pour un seul moyen d’accéder aux éléments du syndicat.

    Si j’ai bien compris, le UB ne peut survenir que si vous essayez de modifier un object déclaré par const.

    Donc le code suivant n’est pas UB:

     int x = 0; const int *cp = &x; int *p = (int*)cp; *p = 1; /* OK: x is not a const object */ 

    Mais c’est UB:

     const int cx = 0; const int *cp = &cx; int *p = (int*)cp; *p = 1; /* UB: cx is const */ 

    L’utilisation d’un syndicat au lieu d’un casting ne devrait faire aucune différence ici.

    Parmi les spécifications C99 (qualificatifs de type 6.7.3):

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

    L’initialisation ne causera certainement pas UB. La conversion entre types de pointeurs qualifiés est explicitement autorisée au § 6.3.2.3 / 2 (n1570 (C11)). C’est l’utilisation du contenu dans ce pointeur par la suite qui cause UB (voir la réponse de @rodrigo ).

    Cependant, vous avez besoin d’une conversion explicite pour convertir un void* en un const void* , car la contrainte d’assignation simple nécessite toujours que tout le qualificatif figurant sur le LHS apparaisse sur le RHS.

    §7.7.9 / 11: … La valeur initiale de l’object est celle de l’expression (après conversion); les mêmes contraintes de type et les mêmes conversions que pour une affectation simple s’appliquent, le type du scalaire étant la version non qualifiée de son type déclaré.

    §6.5.16.1 / 1 : (affectation simple / contraintes)

    • … les deux opérandes sont des pointeurs vers des versions qualifiées ou non qualifiées de types compatibles, et le type pointé vers la gauche contient tous les qualificateurs du type pointé vers la droite;
    • … un opérande est un pointeur sur un type d’object et l’autre est un pointeur sur une version qualifiée ou non qualifiée de void , et le type pointé par la gauche possède tous les qualificateurs du type pointé par la droite;

    Je ne sais pas pourquoi gcc donne juste un avertissement cependant.


    Et pour le truc de l’union, oui ce n’est pas UB, mais le résultat est probablement indéterminé.

    §6.5.2.3 / 3 fn 95 : Si le membre utilisé pour lire le contenu d’un object d’union n’est pas identique au dernier membre utilisé pour stocker une valeur dans l’object, la partie appropriée de la représentation de l’object de la valeur est réinterprétée en tant que représentation d’object dans le nouveau type décrit au 6.2.6 (processus parfois appelé “type punning”). Cela pourrait être une représentation de piège.

    §6.2.6.1 / 7 : Lorsqu’une valeur est stockée dans un membre d’un object de type union, les octets de la représentation d’object qui ne correspondent pas à ce membre mais correspondent à d’autres membres prennent des valeurs non spécifiées. (* Remarque: voir aussi §6.5.2.3 / 6 pour une exception, mais cela ne s’applique pas ici)


    Les sections correspondantes dans n1124 (C99) sont

    • C11 §6.3.2.3 / 2 = C99 §6.3.2.3 / 2
    • C11 §7.7.9 / 11 = C99 §6.7.8 / 11
    • C11 §6.5.16.1 / 1 = C99 §6.5.16.1 / 1
    • C11 §6.5.2.3 / 3 nf 95 = manquant (“le type de frappe” n’apparaît pas en C99)
    • C11 §6.2.6.1 / 7 = C99 §6.2.6.1 / 7

    Ne le jetez pas du tout. C’est un pointeur sur const, ce qui signifie que toute tentative de modification des données n’est pas autorisée et que, dans de nombreuses implémentations, le programme se bloquera si le pointeur pointe vers une mémoire non modifiable. Même si vous savez que la mémoire peut être modifiée, il peut y avoir d’autres pointeurs qui n’attendent pas qu’elle change, par exemple si elle fait partie du stockage d’une chaîne logiquement immuable.

    L’avertissement est là pour une bonne raison.

    Si vous devez modifier le contenu d’un pointeur const, le moyen le plus sûr de le faire est de copier d’abord la mémoire vers laquelle il pointe, puis de le modifier.