Given
T& bindParam(const long& val) { /* some code*/ } T& bindParam(const long&& val) = delete; T& bindParam(const int& val) // same as above for int { static_assert(sizeof(int) == sizeof(long)); // true static_assert(alignof(int) == alignof(long)); // true return bindParam(reinterpret_cast<const long&>(val)); } T& bindParam(const int&& val) = delete;
Clang dit avertissement: reinterpret_cast de 'const int' à 'const long &' a un comportement indéfini [-Wundefined-reinterpret-cast]
( évidemment, violation stricte d'aliasing, mais devrait fonctionner si le compilateur n'est pas destiné à casser le code).
Comment contourner ce problème sans dupliquer le même code pour int
et créer un modèle pour cette méthode n'est pas souhaitable (change la résolution de surcharge)?
3 Réponses :
Comment sécuriser le cast int référence à une longue référence?
sizeof (int)
n'est pas sizeof (long)
, vous ne pouvez pas le faire en toute sécurité. Et la plupart des implémentations x86-64 ont sizeof (int)
étant de 4 octets, et sizeof (long)
étant de 8 octets. Vous voulez également que alignof (int)
soit égal à alignof(long)
Voir également la norme C ++ 17 (ou cette référence ).
il y a static_assert (sizeof (int) == sizeof (long)); // vrai
juste pour ça. Ils sont les mêmes sur Win32 au moins
Alors, est-il possible de faire cela sans déclencher un comportement indéfini, en supposant que int == long?
@OwnageIsMagic: Vous demandez "est-ce que je peux briser l'aliasing strict"? La réponse est non"; ce n'est pas une règle facultative du modèle objet C ++.
évidemment, violation stricte d'aliasing, mais devrait fonctionner si le compilateur n'est pas destiné à casser le code
Si vous ne vous souciez pas des violations strictes d'aliasing, vous devez ignorer les avertissements qui apparaissent lorsque vous enfreignez l'alias strict. C'est ce que fait cet avertissement.
Accéder à un long
via un pointeur / une référence à un int
est une violation de la règle stricte d'aliasing et est donc UB. Ce n'est pas une règle facultative de C ++; il n'y a pas de mécanisme en langue pour ignorer la règle.
Si vous avez décidé d'ignorer les alias stricts, apparemment sous l'hypothèse que le compilateur peut être invoqué pour que votre code se comporte comme vous le souhaitez, vous devez informer votre compilateur en désactivant les avertissements concernant les violations strictes d'aliasing.
/ p>
Donc, si j'ai struct A {};
et struct B {};
, il n'y a aucun moyen en C ++ de passer B à void f (A &); < / code>, non? Il y a des choses comme
std :: is_layout_compatible
et std :: is_standard_layout
, ce qui me donne l'espoir que quelque chose peut être fait.
@OwnageIsMagic: Non. C'est ce que signifie "aliasing strict" (ou plutôt, ce qu'il interdit explicitement: aliaser un objet comme un autre).
J'ai mis à jour la question avec un exemple d'utilisation. Je ne lance que des pointeurs (références) de mon côté et c'est bien défini.
@OwnageIsMagic: Est-ce que ce qui se passe derrière cette fonction est "bien défini" aussi? Comment peut-il utiliser ce pointeur sans le convertir au type correct? Mais dans tous les cas, cela ne change pas ma réponse: en fin de compte, vous violez toujours l'alias strict, que ce soit en C ou en C ++. Vos choix sont soit d'arrêter de faire cela, soit de désactiver l'avertissement.
J'ai trouvé une solution. Voir ma réponse ( stackoverflow.com/a/62940495/5647513 )
int bindParam(const long& val) { return 42; }; int bindParam(const int& val) // same as above for int { static_assert(sizeof(int) == sizeof(long)); // true static_assert(alignof(int) == alignof(long)); // true const int tmp = val; auto p = new (const_cast<int*>(&val)) long(tmp); auto&& ret = bindParam(*p); new (const_cast<int*>(&val)) int(tmp); return std::move(ret); } int main() { int i = 7; //const int i = 7; // UB return bindParam(i); } As long as in input parameter is not const, this is valid.
Qu'entendez-vous par «ceci est valide»? Au moment où vous obtenez un stockage pour ce premier nouvel
appel de placement (ce qui se produit lorsque le nouvel opérateur de placement revient), vous mettez fin à la durée de vie du int
référencé par val < / code>. Ce qui signifie qu'il n'a plus de valeur. Ce qui signifie que lorsque vous essayez d'initialiser le
long
, il ne peut pas copier la valeur, car val
n'en a plus. Ainsi, votre code présente UB.
@Nicol Bolas Belle prise. Je vais introduire tmp
pour enregistrer la valeur.
Vous vous rendez compte que cela "fonctionne" uniquement parce que bindParam (const long &)
n'utilise pas réellement le paramètre, n'est-ce pas? Il atteindra UB une fois qu'il stockera cette référence et essaiera de l'utiliser plus tard, car l'objet auquel il a fait référence verra sa durée de vie terminée par le deuxième placement-nouveau.
@Nicol Bolas Oui. C'est la raison pour laquelle je n'accepte pas cette réponse. La durée de vie des types triviaux n'est pas claire. Dans ma compréhension, la durée de vie d'un objet de type trivial commence quand un stockage suffisant est alloué eel.is/ Projet c ++ / intro.object # 10 et # 28
@Nicol Bolas Pouvez-vous vérifier que le nouveau placement met fin à la durée de vie de l'objet?
D'accord. Je l'ai trouvé Un programme peut mettre fin à la durée de vie de n'importe quel objet en réutilisant le stockage qu'occupe l'objet
eel.is/c++draft/basic.life#5
Je pense que la distribution ici peut être problématique en elle-même si
long
a des exigences d'alignement plus strictes queint
.bindParam (* reinterpret_cast (& val))
, mais j'opterais pour une solution plus sûre:long tmp = val; bindParam (tmp);
code mis à jour, ne vérifie pas l'alignement
@RemyLebeau J'ai besoin d'une référence à une variable externe, pas à une variable locale temporaire
@OwnageIsMagic le
reinterpret_cast
en utilisant des pointeurs au lieu de références accomplirait cela.@RemyLebeau pourquoi ça devrait changer quoi que ce soit?
avertissement: le déréférencement du type 'const long *' qui a été reinterpret_cast du type 'const int *' a un comportement indéfini [-Wundefined-reinterpret-cast]
@OwnageIsMagic: Je suis curieux de savoir pourquoi vous supprimez les versions de rvalue-reference lorsque vous ne modifiez pas la valeur. Y a-t-il une raison pour laquelle les gens ne devraient pas être autorisés à transmettre une valeur pr à cette fonction?
@NicolBolas car il a été enregistré pour une utilisation ultérieure. Il devrait survivre à l'appel de cette fonction.
Aucun compilateur n'est «destiné à casser du code», mais une grande partie du point de règles comme celles-ci est que l'optimisation du bon code et la rupture du mauvais code sont souvent indiscernables pour le compilateur.
@DavisHerring UBsan a été créé pour casser du mauvais code.
@OwnageIsMagic: UBSan fait le contraire: il optimise / casse moins de code afin que le mauvais code ait un comportement plus prévisible / souhaitable (à savoir, la vérification d'exécution que vous voulez).