118 votes

Comment ai-je une valeur supérieure à 8 bits en taille d’un entier de 8 bits ?

J'ai retrouvé un très vilain bug se cachant derrière ce petit bijou. Je suis conscient que pour le C++ spec, signé débordements sont un comportement indéfini, mais seulement lorsque le dépassement de capacité se produit lorsque la valeur est étendu à la largeur de bit sizeof(int). Si je comprends bien, l'incrémentation d'un char ne devrait jamais être un comportement indéfini tant que sizeof(char) < sizeof(int). Mais cela n'explique pas comment c est d'obtenir un impossible de la valeur. Comme un entier 8 bits, comment peut - c contenir des valeurs plus grande que sa largeur de bits?

Code

// Compiled with gcc-4.7.2
#include <cstdio>
#include <stdint.h>
#include <climits>

int main()
{
   int8_t c = 0;
   printf("SCHAR_MIN: %i\n", SCHAR_MIN);
   printf("SCHAR_MAX: %i\n", SCHAR_MAX);

   for (int32_t i = 0; i <= 300; i++)
      printf("c: %i\n", c--);

   printf("c: %i\n", c);

   return 0;
}

Sortie

SCHAR_MIN: -128
SCHAR_MAX: 127
c: 0
c: -1
c: -2
c: -3
...
c: -127
c: -128  // <= The next value should still be an 8-bit value.
c: -129  // <= What? That's more than 8 bits!
c: -130  // <= Uh...
c: -131
...
c: -297
c: -298  // <= Getting ridiculous now.
c: -299
c: -300
c: -45   // <= ..........

Check it out sur ideone.

111voto

hvd Points 42125

C'est un bug du compilateur.

Bien qu'impossible d'obtenir des résultats pour un comportement indéterminé est valide conséquence, il est en fait pas un comportement indéterminé dans votre code. Ce qui se passe est que le compilateur pense que le comportement est indéfini, et d'optimiser en conséquence.

Si c est défini comme int8_t, et int8_t favorise int, alors c-- est censé effectuer la soustraction c - 1 en int de l'arithmétique et de convertir le résultat en int8_t. La soustraction en int ne déborde pas, et en le convertissant en dehors de la plage des valeurs intégrales à un autre type intégral est valide. Si le type de destination est signé, le résultat est la mise en œuvre défini, mais il doit être une valeur valide pour le type de destination. (Et si le type de destination n'est pas signée, le résultat est bien défini, mais qui ne s'applique pas ici.)

15voto

Kaz Points 18072

Un compilateur peut avoir des bugs qui sont autres que les non-conformités à la norme, parce qu'il y a d'autres exigences. Un compilateur doit être compatible avec les autres versions de lui-même. Il peut également être prévu pour être compatible, à certains égards, avec d'autres compilateurs, et aussi pour se conformer à certaines croyances sur le comportement qui sont détenus par la majorité de sa base d'utilisateurs.

Dans ce cas, il semble être une conformité bug. L'expression c-- devraient pas manipuler c d'une manière similaire à l' c = c - 1. Ici, la valeur de c sur le droit est promu type int, puis la soustraction. Depuis c est dans la gamme de int8_t, cette soustraction ne débordera pas, mais il peut produire une valeur qui est hors de la plage de int8_t. Lorsque cette valeur est assignée, la conversion a lieu au retour du type int8_t de sorte que le résultat s'inscrit de nouveau dans c. Dans le hors-de-gamme, la conversion a une mise en valeur définie. Mais une valeur hors de la plage de int8_t n'est pas valide mise en valeur définie. Une mise en œuvre ne peut "définir" que les 8 bits type soudainement détient 9 bits ou plus. Pour que cette valeur soit mise en œuvre définies signifie que quelque chose dans la gamme de int8_t est produit, et le programme se poursuit. La norme C ce qui permet de comportements tels que la saturation de l'arithmétique (commune sur DSP) ou wrap-around (grand public des architectures).

Le compilateur est en utilisant un large sous-jacent type de machine lors de la manipulation des valeurs de l'entier plus petit des types comme int8_t ou char. Lorsque le calcul est effectué, des résultats qui sont en dehors de la plage de la petite de type entier peut être capturé de manière fiable dans ce vaste type. Afin de préserver le comportement visible de l'extérieur que la variable est un 8 bits type, le plus large doit être tronqué dans les 8 bits de large. Code explicite est requis de le faire depuis la machine emplacements de stockage (registres) sont plus larges que les 8 bits et heureux avec les valeurs les plus grandes. Ici, le compilateur négligé de normaliser la valeur et simplement passé à printf . La conversion spécificateur %i en printf n'a aucune idée que l'argument est originaire int8_t calculs; il est juste de travailler avec un int argument.

14voto

Je ne peux pas correspondre à ce dans un commentaire, donc je vais l'afficher comme une réponse.

Pour certains très étrange raison, l' -- de l'opérateur se trouve être le coupable.

J'ai testé le code affiché sur Ideone et remplacé c-- avec c = c - 1 et les valeurs restaient dans la gamme [-128 ... 127]:

c: -123
c: -124
c: -125
c: -126
c: -127
c: -128 // about to overflow
c: 127  // woop
c: 126
c: 125
c: 124
c: 123
c: 122

Bizarre hein? Je ne sais pas beaucoup sur ce que le compilateur ne à des expressions telles que i++ ou i--. Il est probable que la promotion de la valeur de retour d'un int et de le transmettre. C'est la seule conclusion logique, je peux venir avec parce que vous SONT en fait des valeurs qui ne tiennent pas en 8-bits.

12voto

Zoltán Points 2915

Je suppose que le matériel sous-jacent utilise toujours un registre de 32 bits pour le conserver int8_t. Comme la spécification n'impose pas de comportement en cas de dépassement de capacité, l'implémentation ne vérifie pas le dépassement de capacité et permet également de stocker des valeurs plus importantes.


Si vous marquez la variable locale comme volatile vous forcez à utiliser de la mémoire pour obtenir la valeur attendue dans la plage.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X