3
votes

pourquoi tu ne peux pas changer un uint16_t

J'essaie de remplir une variable non signée 64 bits en combinant des valeurs 16 bits et 8 bits:

 left shift count >= width of type [-Wshift-count-overflow]
 uint64_t result = ( hword0 << 32 )

Cela me donne un avertissement.

uint8_t byte0 = 0x00;
uint8_t byte1 = 0xAA;
uint8_t byte2 = 0x00;
uint8_t byte3 = 0xAA;

uint16_t hword0 = 0xAA00;
uint16_t hword1 = 0xAAAA;

uint64_t result = ( hword0 << 32 ) + ( byte3 << 24 ) + 
                  ( byte2 << 16 ) + ( byte1 << 8 ) + ( byte0 << 0  );


3 commentaires

Par exemple, dans byte3 << 24 byte3 ne fait que 8 bits de long, mais vous essayez de le décaler de 24 bits, ce qui ne rentre pas, donc ils ' Je vais me perdre


@AndrewHenle Ce n'est pas défini dans les deux langues.


@eerorika Merci de l'avoir signalé.


7 Réponses :


4
votes

Contrairement à la vignette de votre question, vous pouvez déplacer un uint16_t . Mais vous ne pouvez pas le décaler (sans perte) de plus que sa largeur.

Le type de votre opérande d'entrée est appliqué également à l'opérande de sortie , donc dans votre question d'origine, vous avez un uint16_t qui vaut 0 (car toute valeur décalée de 32 vers la gauche puis coupée à 16 bits est 0), et le sont donc presque toutes vos valeurs uint8_t .

La solution est simple: avant de décaler, transtypez vos valeurs dans le type approprié adapté au décalage:

uint64_t result = ( (uint64_t)hword0 << 32 ) + 
( (uint32_t)byte3 << 24 ) + ( (uint32_t)byte2 << 16 ) + ( (uint32_t)byte1 << 8  ) + ( (uint32_t)byte0 << 0  );


3 commentaires

Re: Avec int 32 bits et uint16_t << 32 , le "... puis découpé en 16 bits est ..." mal conduit. En C, la valeur n'est pas "tronquée" à 16 bits - le type de l'opérande dans ce décalage est int . uint16_t << 32 est UB car le décalage n'est pas compris entre 0 et 31 (largeur int ). OTOH, la solution est bonne.


@chux 'le type de l'opérande dans ce décalage est int ' Non. Comme cela a déjà été discuté, << est l'un des opérandes qui ne convertissent pas implicitement leur argument en < code> int . Et si tel était le cas, il serait unsigned int , où << 32 n'est pas UB.


C, comme post est étiqueté, spécifie "6.5.7 Opérateurs de décalage de bits" Les promotions d'entiers sont effectuées sur chacun des opérandes. Le type du résultat est celui de l'opérande gauche promu "Sur un système int 32 bits, uint16_t est promu en int , en non signé sur un format 16 bits. Le lien de cette réponse traite des "conversions arithmétiques habituelles"



0
votes

octet2

déplace à gauche une valeur de 8 octets de 16 octets. Cela ne fonctionnera pas. Par 6.5.7 Opérateurs de décalage de bits , paragraphe 4 de la norme C :

Le résultat de E1

Puisque vous utilisez un décalage vers la gauche sur les valeurs non signées, vous obtenez zéro.

MODIFIER

Selon le paragraphe 3 de la même section , il s'agit en fait d'un comportement non défini: p >

Si la valeur de l'opérande droit est négative ou est supérieure ou égale à la largeur de l'opérande gauche promu, le comportement n'est pas défini.

Vous voulez quelque chose comme

( ( uint64_t ) byte2 ) << 16

La conversion en une valeur de 64 bits garantira que le résultat ne perd pas de bits.

blockquote>

4 commentaires

Est-ce vraiment comme ça, ou est-ce que tous les opérandes sont d'abord convertis en unsigned int ? Dans ce cas, seul le premier opérande de la somme serait affecté.


@glglgl Voir stackoverflow.com/questions/3482262/…


Oh merci. Cela m'aide.


"déplace à gauche une valeur de 8 octets de 16 octets." et "en utilisant un décalage vers la gauche sur des valeurs non signées, vous obtenez zéro." -> En C, la valeur de 8 octets est d'abord promue en int . Ensuite, les règles de décalage signé et de largeur de int s'appliquent.



7
votes

hword0 a une longueur de 16 bits et vous demandez un décalage de 32 bits. Le décalage de plus que le nombre de bits - 1 n'est pas défini.

La solution consiste à convertir vos composants vers le type de destination: uint64_t result = (((uint64_t) hword0) << 32) + etc.


11 commentaires

Ce n'est pas défini pour les entiers signés, non?


@FiddlingBits à droite, il faut le reformuler pour le bon cas!


Ce comportement n'est pas défini pour les non-signés également


@Kevin Pour C aussi?


@kevin Non seulement pour le décalage à droite. Le décalage gauche de non signé est bien défini.


@FiddlingBits, je le crois. C'est une limitation des architectures qui utilisent un nombre limité de bits pour stocker l'opérande de décalage.


@ Jean-BaptisteYunès Ah vous avez peut-être raison. Je pensais que c'était indéfini pour les deux.


@Kevin Non, le décalage vers la gauche C sur non signé est modulo.


@Kevin Je m'excuse pour mon erreur, tu avais raison! déplacer plus que la taille est un comportement indéfini.


@AndrewHenle Pas de module, UB. Re: "Si la valeur de l'opérande droit est négative ou est supérieure ou égale à la largeur de l'opérande gauche promu, le comportement n'est pas défini." §6.5.7 Indépendant de r / l, sign-ness.


Détail: En C, "Décaler plus que le nombre de bits - 1" -> dans le type promu "n'est pas défini".



2
votes

Selon l'avertissement, 32 bits est supérieur ou égal à la taille de l'opérande sur le système cible. Le standard C ++ dit:

[expr.shift]

Les opérandes doivent être de type énumération intégrale ou non et des promotions intégrales sont effectuées. Le type de résultat est celui de l'opérande gauche promu. Le comportement n'est pas défini si l'opérande de droite est négatif, ou supérieur ou égal à la longueur en bits de l'opérande de gauche promu .

Règle correspondante du standard C:

Opérateurs de décalage au niveau du bit

Les promotions entières sont effectuées sur chacun des opérandes. Le type du résultat est celui de l'opérande gauche promu. Si la valeur de l'opérande de droite est négative ou est supérieure ou égale à la largeur de l'opérande de gauche promu, le comportement n'est pas défini .

Selon la règle citée, le comportement de votre programme n'est pas défini qu'il soit écrit en C ou C ++.

Vous pouvez résoudre le problème en convertissant explicitement l'opérande de gauche du décalage en un type non signé suffisamment grand.

P.S. Sur les systèmes où uint16_t est plus petit que int (ce qui est assez typique), un oprand uint16_t sera promu en int lorsqu'il est utilisé comme opérande arithmétique. En tant que tel, byte2 << 16 n'est pas inconditionnellement indéfini sur de tels systèmes. Vous ne devriez pas vous fier à ce détail, mais cela explique pourquoi vous ne voyez aucun avertissement du compilateur concernant ce changement.

byte2 << 16 peut toujours être indéfini si le résultat est en dehors de la plage de valeurs représentables de type (signé) int . Il serait bien défini si le type promu n'était pas signé.


3 commentaires

@ Jean-BaptisteYunès qui contredit ce que dit la norme. Avez-vous une règle standard pour étayer cela?


Vous avez raison, j'ai mal interprété le paragraphe qui suit dans la norme. Celui-ci est le bon en premier lieu!


@ Jean-BaptisteYunès moi aussi.



4
votes

Vous pouvez décaler un uint16_t . Ce que vous ne pouvez pas faire, c'est décaler une valeur entière d'un nombre supérieur ou égal à la taille du type. Cela provoque le comportement indéfini . Ceci est documenté dans la section 6.5.7p3 du C standard concernant les opérateurs de décalage au niveau du bit:

Les promotions entières sont effectuées sur chacun des opérandes. le le type du résultat est celui de l'opérande gauche promu. Si la valeur de l'opérande de droite est négative ou est supérieure à ou égal à la largeur de l'opérande gauche promu, le comportement est indéfini.

On pourrait penser que cela signifie que tout décalage supérieur ou égal à 16 sur un uint16_t n'est pas valide. Cependant , comme mentionné ci-dessus, les opérandes de l'opérateur font l'objet d'une promotion d'entiers . Cela signifie que toute valeur avec un rang inférieur à int est promue en int avant d'être utilisée dans une expression. Donc, si int est de 32 bits sur votre système, alors vous pouvez décaler jusqu'à 31 bits à gauche.

C'est pourquoi (byte3 ne génère pas d'avertissement même si byte est un uint8_t tandis que (hword0 ne l'est pas. Cependant, il y a toujours un problème ici en raison de la promotion vers int . Étant donné que la valeur promue est maintenant signée, vous courez le risque de déplacer un 1 dans le bit de signe. Cela appelle également un comportement non défini.

Pour résoudre ce problème, toute valeur décalée vers la gauche de 32 ou plus doit d'abord être transtypée en uint64_t afin que la valeur puisse être appliquée correctement, ainsi que toute valeur qui peut finir par déplacer un 1 dans le bit de signe:

 uint64_t result = ( (uint64_t)hword0 << 32 ) + 
    ( (uint64_t)byte3 << 24 ) + ( (uint64_t)byte2 << 16 ) + 
    ( (uint64_t)byte1 << 8  ) + ( byte0 << 0  );


0 commentaires

-1
votes

Pour faire ce que vous voulez faire, l'idée clé est d'utiliser un uint64_t intermédiaire (la taille finale) dans lequel mélanger les bits.

Les compilations suivantes sans avertissements:

vous pouvez utiliser la promotion automatique (et aucune diffusion)

//           vvvvvvvv ---- formal parameter
uint64_t sc (uint64_t ui64) {
  return static_cast<uint64_t>(ui64);
}


     // using static cast function
     {
        uint64_t result = (
           (sc(hword0) << 32) |
           (sc(byte3)  << 24) |
           (sc(byte2)  << 16) |
           (sc(byte1)  <<  8) |
           (sc(byte0)  <<  0)
           );

        cout << "\n  " << hex << result << endl;
     }

vous pouvez également utiliser la diffusion statique (plusieurs times):

     {
        uint64_t result = (
           (static_cast<uint64_t>(hword0) << 32) |
           (static_cast<uint64_t>(byte3)  << 24) |
           (static_cast<uint64_t>(byte2)  << 16) |
           (static_cast<uint64_t>(byte1)  <<  8) |
           (static_cast<uint64_t>(byte0)  << 0 )
           );

        cout << "\n  " << hex << result << endl;
     }

Et vous pouvez faire les deux en créant une fonction pour a) effectuer le cast statique et b) avec un paramètre formel pour que le compilateur effectue une promotion automatique.

La fonction ressemble à:

     {
        uint64_t b4567 = hword0; // auto promotion
        uint64_t b3    = byte3;
        uint64_t b2    = byte2;
        uint64_t b1    = byte1;
        uint64_t b0    = byte0;

        uint64_t result =  (
           (b4567 << 32) |
           (b3    << 24) |
           (b2    << 16) |
           (b1    <<  8) |
           (b0    <<  0)   );
     }


1 commentaires

De mon expérience matérielle, quand je change de peu, je préfère | au lieu de + (et & pour le masquage)



-1
votes

Du point de vue C:

De nombreuses discussions ici omettent qu'un uint8_t appliqué à un quart de travail (gauche ou droite) est d'abord promu en int , puis les règles de décalage sont appliquées.

La même chose se produit avec uint16_t lorsque int est 32 bits. (17 bits ou plus)


Lorsque int est 32 bits

hword0 est UB due à la quantité de décalage trop grande: en dehors de 0 à 31.
byte3 est UB lors d'une tentative de passage dans le bit de signe . byte3 & 0x80 est vrai.

Les autres décalages sont OK.


Si int était 64 bits, OP le code d'origine est correct - pas d'UB, y compris hword0 .


Si int avait été 16 bits, tous les décalages du code ( à part ) sont UB ou UB potentiel.


Pour ce faire, sans lancer (quelque chose que j'essaie d'éviter), prendre en compte

uint64_t result = (1ull*hword0 << 32) + (1ul*byte3 << 24) + (1ul*byte2 << 16) +
    (1u*byte1 << 8) + byte0;

Ou

// uint64_t result = (hword0 << 32) + (byte3 << 24) + (byte2 << 16) + (byte1 << 8) + byte0

// Let an optimizing compiler do its job
uint64_t result = hword0;
result <<= 8;
result += byte3;
result <<= 8;
result += byte2;
result <<= 8;
result += byte1;
result <<= 8;
result += byte0;


0 commentaires