C'est en effet un cas particulier intéressant. Il ne se produit ici que parce que vous utilisez uint16_t
pour le type non signé lorsque l'architecture utilise 32 bits pour le type non signé. ìnt
Voici un extrait de Clause 5 Expressions du projet n4296 pour C++14 (c'est moi qui souligne) :
10 De nombreux opérateurs binaires qui attendent des opérandes de type arithmétique ou énumération provoquent des conversions .... Ce schéma est appelé les conversions arithmétiques habituelles, qui sont définies comme suit :
...
(10.5.3) - Sinon, si l'opérande qui a Le type d'entier non signé a un rang supérieur ou égal à celui de l'élément rang du type de l'autre opérande l'opérande de type entier signé est converti en un type d'opérande de type entier non signé. le type de l'opérande avec un type d'entier non signé.
(10.5.4) - Sinon, si le type de l'opérande à type d'entier signé peut représenter toutes les valeurs de le type de l'opérande avec le type entier non signé l'opérande dont le type est un entier non signé est être converti en type d'opérande de type entier signé.
Vous êtes dans le cas de la 10.5.4 :
-
uint16_t
n'est que de 16 bits alors que int
est de 32
-
int
peut représenter toutes les valeurs de uint16_t
Ainsi, le uint16_t check = 0x8123U
est converti en l'opérande signé 0x8123
et le résultat de l'analyse binaire &
est toujours 0x8123.
Mais le décalage (par bit, donc au niveau de la représentation) fait que le résultat est le non signé intermédiaire 0x81230000 qui, converti en un int, donne une valeur négative (techniquement, c'est défini par l'implémentation, mais cette conversion est un usage courant).
5.8 Opérateurs de décalage [expr.shift].
...
Sinon, si E1 a un type signé et une valeur non négative, et E1×2 E2 est représentable dans le type non signé correspondant du type de résultat, alors cette valeur, convertie en type de résultat, est la valeur résultante ;...
et
4.7 Conversions intégrales [conv.integral].
...
3 Si le type de destination est signé, la valeur est inchangée si elle peut être représentée dans le type de destination ; sinon, la valeur est définie par la mise en œuvre .
(attention, il s'agissait d'un véritable comportement indéfini en C++11...)
On obtient donc une conversion de l'int signé 0x81230000 en un uint64_t
qui comme prévu donne 0xFFFFFFFF81230000, car
4.7 Conversions intégrales [conv.integral].
...
2 Si le type de la destination est non signé, la valeur résultante est le plus petit entier non signé congruent à l'entier source. source (modulo 2n où n est le nombre de bits utilisés pour représenter le type non signé).
TL/DR : Il n'y a pas de comportement indéfini ici, ce qui cause le résultat est la conversion de signed 32 bits int en unsigned 64 bits int. La seule partie qui est comportement indéfini est un décalage qui causerait un dépassement de signe, mais toutes les implémentations communes partagent celui-ci et c'est mise en œuvre définie dans la norme C++14.
Bien sûr, si vous forcez le second opérande à être non signé, tout est non signé et vous obtenez évidemment la bonne réponse. 0x81230000
résultat.
[Comme expliqué par MSalters, le résultat du décalage est seulement mise en œuvre définie depuis C++14, mais était en effet comportement indéfini en C++11. Le paragraphe sur l'opérateur shift dit :
...
Sinon, si E1 a un type signé et une valeur non négative, et E1×2 E2 est représentable dans le type de résultat alors c'est la valeur qui en résulte ; sinon, le comportement est indéfini .