web-dev-qa-db-fra.com

Lorsqu'un int est converti en un court et tronqué, comment la nouvelle valeur est-elle déterminée?

Quelqu'un peut-il clarifier ce qui se passe lorsqu'un entier est converti en short en C? J'utilise Raspberry Pi, donc je sais qu'un int est de 32 bits, et donc un short doit être de 16 bits.

Disons que j'utilise le code C suivant par exemple:

int x = 0x1248642;
short sx = (short)x;
int y = sx;

J'obtiens que x serait tronqué, mais quelqu'un peut-il expliquer exactement comment? Les quarts sont-ils utilisés? Comment exactement un nombre est-il tronqué de 32 bits à 16 bits?

33
Bolboa

Selon la norme ISO C, lorsque vous convertissez un entier en un type signé et que la valeur est en dehors de la plage du type cible, le résultat est défini par l'implémentation. (Ou un signal défini par l'implémentation peut être généré, mais je ne connais aucun compilateur qui le fasse.)

En pratique, le comportement le plus courant est que les bits de poids fort sont rejetés. En supposant que int est de 32 bits et short de 16 bits, la conversion de la valeur 0x1248642 produira probablement un motif de bits qui ressemble à 0x8642. Et en supposant une représentation en complément à deux pour les types signés (qui est utilisée sur presque tous les systèmes), le bit de poids fort est le bit de signe, donc la valeur numérique du résultat sera -31166.

int y   =   sx;

Cela implique également une conversion implicite, de short vers int. Étant donné que la plage de int est garantie pour couvrir au moins toute la plage de short, la valeur est inchangée. (Étant donné que, dans votre exemple, la valeur de sx est négative, ce changement de représentation impliquera probablement extension de signe, propageant le 1 bit de signe pour les 16 bits de poids fort du résultat.)

Comme je l'ai indiqué, aucun de ces détails n'est requis par la norme linguistique. Si vous voulez vraiment tronquer des valeurs en un type plus étroit, il est probablement préférable d'utiliser des types non signés (qui ont un comportement enveloppant spécifié par la langue) et peut-être des opérations de masquage explicites, comme ceci:

unsigned int x = 0x1248642;
unsigned short sx = x & 0xFFFF;

Si vous avez une quantité 32 bits que vous souhaitez insérer dans une variable 16 bits, la première chose à faire est de décider comment vous voulez que votre code se comporte si la valeur ne correspond pas. Une fois que vous avez décidé cela, vous pouvez comprendre comment écrire du code C qui fait ce que vous voulez. Parfois, la troncature est ce que vous voulez, auquel cas votre tâche sera facile, surtout si vous utilisez des types non signés. Parfois, une valeur hors plage est une erreur, auquel cas vous devez la vérifier et décider comment gérer l'erreur. Parfois, vous souhaiterez peut-être que la valeur soit saturée plutôt que tronquée, vous devrez donc écrire du code pour ce faire.

Il est important de savoir comment les conversions fonctionnent en C, mais si vous commencez avec cette question, vous pourriez peut-être aborder votre problème dans la mauvaise direction.

38
Keith Thompson

La valeur 32 bits est tronquée à 16 bits de la même manière qu'un pain aux bananes de 32 cm de long serait coupé si vous le confiturez dans un moule de 16 cm de long. La moitié de celui-ci rentrerait et serait toujours un pain aux bananes, et le reste serait "parti".

11
Amit

La troncature se produit dans les registres CPU. Ceux-ci ont des tailles différentes: 8/16/32/64 bits. Maintenant, vous pouvez imaginer un registre comme:

<--rax----------------------------------------------------------------> (64-bit)
                                    <--eax----------------------------> (32-bit)
                                                      <--ax-----------> (16-bit)
                                                      <--ah--> <--al--> (8-bit high & low)
01100011 01100001 01110010 01110010 01111001 00100000 01101111 01101110

x reçoit d'abord la valeur 32 bits 0x1248642. En mémoire *, cela ressemblera à:

-----------------------------
|  01  |  24  |  86  |  42  |
-----------------------------
 31..24 23..16 15..8  7..0       

Maintenant, le compilateur charge x dans un registre. De là, il peut simplement charger les 16 bits les moins significatifs (à savoir ax) et les stocker dans sx.


* L'endianité n'est pas prise en compte par souci de simplicité

6
edmz

Laissez peut-être le code parler de lui-même:

#include <stdio.h>

#define BYTETOBINARYPATTERN "%d%d%d%d%d%d%d%d"
#define BYTETOBINARY(byte)  \
   ((byte) & 0x80 ? 1 : 0), \
   ((byte) & 0x40 ? 1 : 0), \
   ((byte) & 0x20 ? 1 : 0), \
   ((byte) & 0x10 ? 1 : 0), \
   ((byte) & 0x08 ? 1 : 0), \
   ((byte) & 0x04 ? 1 : 0), \
   ((byte) & 0x02 ? 1 : 0), \
   ((byte) & 0x01 ? 1 : 0) 

int main()
{
    int x    =   0x1248642;
    short sx = (short) x;
    int y    =   sx;

    printf("%d\n", x);
    printf("%hu\n", sx);
    printf("%d\n", y);

    printf("x: "BYTETOBINARYPATTERN" "BYTETOBINARYPATTERN" "BYTETOBINARYPATTERN" "BYTETOBINARYPATTERN"\n",
        BYTETOBINARY(x>>24), BYTETOBINARY(x>>16), BYTETOBINARY(x>>8), BYTETOBINARY(x));

    printf("sx: "BYTETOBINARYPATTERN" "BYTETOBINARYPATTERN"\n",
        BYTETOBINARY(y>>8), BYTETOBINARY(y));

    printf("y: "BYTETOBINARYPATTERN" "BYTETOBINARYPATTERN" "BYTETOBINARYPATTERN" "BYTETOBINARYPATTERN"\n",
        BYTETOBINARY(y>>24), BYTETOBINARY(y>>16), BYTETOBINARY(y>>8), BYTETOBINARY(y));

    return 0;
}

Sortie:

19170882
34370
-31166

x: 00000001 00100100 10000110 01000010
sx: 10000110 01000010
y: 11111111 11111111 10000110 01000010

Comme vous pouvez le voir, int -> short renvoie les 16 bits inférieurs, comme prévu.

La conversion de short en int produit le short avec les 16 bits de poids fort définis. Cependant, je soupçonne qu'il s'agit d'un comportement spécifique à l'implémentation et non défini. Vous interprétez essentiellement 16 bits de mémoire comme un entier, qui lit 16 bits supplémentaires de tout ce qui se trouve là (ou 1 si le compilateur est agréable et veut vous aider à trouver les bogues plus rapidement).

Je pense qu'il devrait être sûr de faire ce qui suit:

int y = 0x0000FFFF & sx;

Évidemment, vous ne récupérerez pas les bits perdus, mais cela garantira que les bits élevés sont correctement mis à zéro.

Si quelqu'un peut vérifier le comportement des bits courts -> int élevés avec une référence faisant autorité, ce serait apprécié.

Remarque: macro binaire adaptée de cette réponse .

4
Dan Bechard

Les 16 bits les plus élevés sont simplement coupés de l'entier. Par conséquent, votre court métrage deviendra 0x8642 qui est en fait un nombre négatif -31166.

sx la valeur sera la même que les 2 octets les moins significatifs de x, dans ce cas ce sera 0x8642 qui (s'il est interprété comme un entier signé 16 bits) donne -31166 en décimal.

2
nsilent22