0
votes

Comment sécuriser la référence de cast int à une référence longue?

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)?


11 commentaires

Je pense que la distribution ici peut être problématique en elle-même si long a des exigences d'alignement plus strictes que int .


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).


3 Réponses :


1
votes

Comment sécuriser le cast int référence à une longue référence?

Sur les implémentations où 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 ).


3 commentaires

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 ++.



2
votes

é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>


5 commentaires

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 )



-1
votes
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.

6 commentaires

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