4
votes

Ce code d'opérateur de bits est-il des effets secondaires (terme utilisé dans le livre K&R C) ou des instructions de traitement dépendant de la machine?

Voici deux codes qui semblent faire la même chose, mais ce n'est pas le cas. Ces deux différences lors de l'exécution et de la comparaison de la sortie avec le traçage donnent une confusion car il semble que le premier traitement de code est un code dépendant de la machine. Veuillez lire les deux codes

Code 1: -

unsigned char c=(~0 << 3);
c >>= 4;
c <<= 1;
printf("%d", c);

Sortie: - 254

Code 2: -

XXX

Sortie: -. 30

La sortie du code ci-dessus est différente.

Non seulement ce code (1er code) est source de confusion, mais tous les types de code impliquant un opérateur de décalage bit à bit sur une seule ligne donnent des résultats inattendus.

Le deuxième code fonctionne correctement.

Veuillez exécuter ce code sur votre ordinateur et vérifier la sortie ci-dessus

ET / OU

Expliquez pourquoi ces sorties ne sont pas les mêmes.

OU

Enfin, nous devons apprendre que nous ne devons pas appliquer plusieurs opérateurs de décalage au niveau du bit dans notre code.

Merci p >


5 commentaires

La différence réside dans les types. Voyez ce qui se passe avec unsigned char c = ((unsigned char) ((unsigned char) (~ 0 << 3) >> 4) << 1);


il existe des conversions arithmétiques par défaut impliquées dans la coercition qui font la différence.


Le calcul du premier code est fait en utilisant le type int afin que les bits autres que les 8 bits les moins significatifs soient préservés. Avec le second, les affectations abandonnent toutes sauf les 8 bits les moins significatifs, modifiant ainsi complètement le calcul.


@JohnKugelman Beaucoup de matériel d'étude dans la petite FAQ C de SO, y compris Règles de promotion de type implicite , Que sont les opérateurs de décalage de bits (décalage de bits) et comment fonctionnent-ils? et Comportement non défini, non spécifié et défini par l'implémentation .


Bien que dans ce cas, il n'y ait pas beaucoup de conversions implicites sur lesquelles écrire. Il y a une conversion de lvalue en caractère non signé. Sinon, le type du résultat d'une expression de décalage est celui de son opérande gauche (promu).


5 Réponses :


11
votes

~ 0 << 3 est toujours un bogue, aucun des deux exemples n'est correct.

  • 0 est de type int qui est signé.
  • ~ 0 convertira le contenu binaire en tous les uns: 0xFF ... FF .
  • Lorsque vous laissez les données de décalage dans le bit de signe d'un entier signé, vous invoquez un comportement non défini. Même chose si vous déplacez à gauche un entier négatif.

Conclusion: aucun des deux exemples n'a une sortie déterministe et les deux peuvent planter ou afficher des déchets.


0 commentaires

-2
votes

Dans le premier cas, il y a une coercition vers char seulement après la fin du calcul tandis que dans le second cas il y a une coercition vers char après le premier (~ 0 << 3) .

comme je l'ai dit dans les commentaires, il existe des conversions arithmétiques par défaut impliquées dans la coercition qui font la différence.


7 commentaires

Je pense que vous voulez dire conversion . Quoi qu'il en soit, il n'y a pas de comportement déterministe du code, donc le raisonnement sur les conversions de type implicites n'est pas significatif.


Les conversions automatiques @Lundin sont également appelées coercitions.


Non, ils ne le sont pas.


@alinsoar: Pouvez-vous citer une section de la norme C pour étayer cela?


@Lundin, "Ce type de conversion de type peut être effectué implicitement ou explicitement. Conversion implicite, également appelée coercition" - en.wikibooks.org/wiki/Introduction_to_Programming_Languages/‌…


Ceci est étiqueté C qui est défini par ISO 9899. Tout le reste, y compris les wikis Internet trash, n'est pas pertinent. Une conversion peut être implicite ou explicite. Un casting est toujours une conversion explicite. Les termes cast (C11 6.5.4), conversion implicite (C11 6.3) et conversion explicite (C11 6.3) sont tous des termes formels, définis par la norme C. Le mot «coercition» n'existe même pas dans le document standard C.


Ok, alors maintenant j'ai cherché tous les livres C que j'ai au format pdf et je ne les ai trouvés nulle part, sauf pour un endroit: dans "Traps & Pitfalls" de Koenig. C'est un livre semi-canonique donc je suppose que vous avez un point. Cependant, le fait demeure qu'il s'agit d'argot et non d'un terme formel.



3
votes

Premièrement, ~ 0 appelle comportement non défini car ~ 0 est une valeur entière signée avec tous les bits mis à 1 et vous vous déplacez ensuite à gauche dans le bit de signe.

Changer ceci en ~ 0u empêche UB mais imprime le même résultat, la question est donc de savoir pourquoi.

Nous avons donc d'abord ceci:

unsigned char c = (~0 << 3);

De type unsigned int . C'est au moins 16 bits donc la valeur est:

0xfe

Alors ceci:

0x1ffe

Vous donne: p >

(((~0 << 3) >> 4) << 1)

Alors ceci:

0x0fff

Vous donne:

((~0 << 3) >> 4)

Et ceci :

0xfff8

Vous donne:

`~0u << 3`

Attribuer cette valeur à un caractère non signé couper efficacement jusqu'à l'octet de poids faible:

0xffff

Donc, il affiche 254.

Maintenant, dans le deuxième cas, vous commencez par ceci:

XXX

De ci-dessus, cela assigne 0xfff8 à c qui est tronqué à 0xf8 . Ensuite, >> 4 vous donne 0x0f et vous donne 0x1e qui vaut 30.


2 commentaires

Le problème est que vous ne pouvez pas raisonner comme vous le faites, car s'il y a un type signé impliqué, il n'y a pas seulement UB, mais aussi un comportement défini par l'implémentation du décalage à droite 0xFF ... FF. Cela peut être un décalage logique ou arithmétique. Mais dans votre exemple, vous forcez le décalage logique en changeant le type en non signé, vous avez donc changé la signification du code.


@Lundin peut-être en général, mais dans ce cas, cela ne change pas ce qui est déplacé dans l'octet d'ordre le plus bas, le résultat est donc le même.



0
votes

J'ai compilé (avec x86-64 gcc 9.1 ) ces deux lignes:

main:
        push    rbp
        mov     rbp, rsp
        mov     BYTE PTR [rbp-1], -2
        mov     BYTE PTR [rbp-2], -2
        mov     eax, 0
        pop     rbp
        ret

Et j'ai obtenu la sortie d'assembly suivante:

int main() {
    unsigned char e=(~0 << 1);

    unsigned char d=(((~0 << 3) >> 4) << 1);
}

Comme vous pouvez le voir, les deux lignes sont converties dans la même instruction mov BYTE PTR [rbp-1], -2 . Donc, il semble que le compilateur effectue une optimisation avec votre premier code.


0 commentaires

0
votes

Merci à Thomas Jager pour sa réponse (donnée sur le commentaire de la question)

La solution est simple.

Dans le 1er code, la manipulation des bits est effectuée en prenant l'opérande comme char signé. Pour cette raison, deux nombres binaires complémentaires continuent de changer leur modèle de bits pendant que la manipulation de bits est en cours. Après cela, le nombre de complément à deux résultats est converti en nombre positif avant d'être affecté à la variable non signée c. Le résultat est donc finalement 254.

La question est d'expliquer pourquoi deux sorties de code sont différentes. Nous savons tous que Code 2nd fonctionne bien. Par conséquent, je n'explique que pourquoi le code 1 ne fonctionne pas correctement.

1er code: -

Step 1:   ~0 -----> -1 ----(binary form)----> 11111111 with sign bit 1 (means negative)


Step 2:   (sign bit 1)11111111 << 3 -----shifting to left----> (sign bit 1)11111000


Step 3 ***:   (sign bit 1)11111000 >> 4 ----shifing to right-----> (sign bit 1)11111111

*[*** - The left most bits is 1 in Result because of sign extension 
     Sign bit 1 retain its bit to 1 but right shifting the number will append 1 to 
left most bits without modify sign bit to 0 . 
     Hence all left most bit append to 1 because sign bit 1 is supplying this 1 to 
left most bits while right shifting                                          ]*


Step 4:    (sign bit 1)11111111 << 1 ---shifting to left---> (sign bit 1)11111110


Step 5:    two complement number (sign bit 1)11111110 converted to positive number 
            by deleting only sign bit to 0.


Step 6:    Result : (sign bit 0)11111110 ---decimal equivalent---> 254

Le traçage du 1er code est le suivant: - p >

unsigned char c=(((~0 << 3) >> 4) << 1);
printf("%d", c);

Je viens d'expliquer sa réponse.

Merci à tous d'avoir fait l'effort de répondre à cette question.


0 commentaires