Cette question est motivée par me mettre en œuvre des algorithmes cryptographiques (par exemple SHA-1) en C/C++, écrit portable de la plate-forme agnostique code, et soigneusement éviter un comportement indéfini.
Supposons qu'un standardisé algorithme de chiffrement vous demande de mettre en œuvre ce:
b = (a << 31) & 0xFFFFFFFF
où a
et b
sont des entiers 32 bits non signés. Remarquez que dans la suite, nous supprimons tous les bits au-dessus de la moins importante à 32 bits.
Dans un premier naïf approximation, on peut supposer que l' int
32 bits de large sur la plupart des plates-formes, donc on peut écrire:
unsigned int a = (...);
unsigned int b = a << 31;
Nous savons que ce code ne fonctionne pas partout, parce que int
est de 16 bits de large sur certains systèmes, 64 bits sur les autres, et peut-être même de 36 bits. Mais à l'aide de stdint.h
, nous pouvons améliorer ce code avec l' uint32_t
type:
uint32_t a = (...);
uint32_t b = a << 31;
Nous sommes donc à faire, non? C'est ce que j'ai pensé pendant des années. ... Pas tout à fait. Supposons que sur une certaine plate-forme, nous avons:
// stdint.h
typedef unsigned short uint32_t;
La règle pour effectuer des opérations arithmétiques en C/C++, c'est que si le type (comme short
) est plus étroite que int
, puis il est élargi à l' int
si toutes les valeurs peuvent s'adapter, ou unsigned int
sinon.
Disons que le compilateur définit short
32 bits (signé) et int
48 bits (signé). Ensuite, ces lignes de code:
uint32_t a = (...);
uint32_t b = a << 31;
effectivement signifier:
unsigned short a = (...);
unsigned short b = (unsigned short)((int)a << 31);
Notez que a
est promu int
parce que tout d' ushort
(c - uint32
) s'inscrit dans int
(c - int48
).
Mais maintenant, nous avons un problème: déplacement non-zéro les bits à gauche dans le bit de signe d'un entier signé de type est un comportement indéfini. Ce problème est arrivé parce que notre uint32
a été promu int48
- au lieu d'être promu uint48
(où de gauche décalage serait bien).
Voici mes questions:
Est mon raisonnement correct, et est-ce un problème légitime dans la théorie?
Ce problème est-il sûr d'ignorer parce que sur chaque plate-forme de l'entier suivant le type est le double de la largeur?
Est une bonne idée de correctement se défendre contre cette situation pathologique par pré-masquage de l'entrée comme ceci?:
b = (a & 1) << 31;
. (Ce sera nécessairement correct sur chaque plate-forme. Mais cela pourrait permettre à une vitesse critiques de l'algorithme de chiffrement plus lentement que nécessaire.)
Précisions/modifications:
Je vais accepter les réponses pour le C ou C++, ou les deux. Je veux savoir la réponse à au moins l'une des langues.
La pré-masquage logique peut nuire à peu de rotation. Par exemple, GCC compiler
b = (a << 31) | (a >> 1);
32 bits de rotation de bits de l'instruction en langage d'assemblage. Mais si nous pré-masque le virage à gauche, il est possible que la nouvelle logique n'est pas traduit en peu de rotation, ce qui signifie maintenant 4 opérations sont effectuées au lieu de 1.