Question

Voici deux programmes très simples. Je vous attendre à obtenir la même sortie, mais je ne pas. Je ne peux pas comprendre pourquoi. Les premières sorties 251. Les secondes sorties -5. Je peux comprendre pourquoi le 251. Cependant, je ne vois pas pourquoi le second programme me donne -5.

PROGRAMME 1:

#include <stdio.h>

int main()
{

unsigned char  a;
unsigned char  b;
unsigned int  c;

a = 0;
b= -5;

c =  (a + b);

printf("c hex: %x\n", c);
printf("c dec: %d\n",c);

}

Sortie:

c hex: fb
c dec: 251

PROGRAMME 2:

#include <stdio.h>

int main()
{

unsigned char  a;
unsigned char  b;
unsigned int  c;

a = 0;
b=  5;

c =  (a - b);

printf("c hex: %x\n", c);
printf("c dec: %d\n",c);

}

Sortie:

c hex: fffffffb
c dec: -5
Était-ce utile?

La solution

Il y a deux questions distinctes ici. Le premier est le fait que vous obtenez différentes valeurs hexagonales pour ce qui ressemble les mêmes opérations. Le fait sous-jacente que vous manque est que chars sont promus à ints (tout comme shorts) pour faire de l'arithmétique. Voici la différence:

a = 0  //0x00
b = -5 //0xfb
c = (int)a + (int)b

Ici, a est étendu à 0x00000000 et b est étendu à 0x000000fb ( pas signe étendu, car il est un non signé char). Ensuite, l'addition est réalisée, et nous obtenons 0x000000fb.

a = 0  //0x00
b = 5  //0x05
c = (int)a - (int)b

Ici, a est étendu à 0x00000000 et b est étendu à 0x00000005. Ensuite, la soustraction est effectuée, et nous obtenons 0xfffffffb.

La solution? Stick avec chars ou ints; leur mélange peut provoquer des choses que vous n'attendre.

Le deuxième problème est qu'un unsigned int est en cours d'impression comme -5, clairement une valeur signée. Cependant, dans la chaîne, vous avez dit printf d'imprimer son deuxième argument, interprété comme un entier signé (ce qui est des moyens de "%d"). L'astuce est que printf ne sait pas ce que les types des variables que vous passer. Ce simple interprète eux la façon dont la chaîne indique à. Voici un exemple où nous disons printf d'imprimer un pointeur comme un entier:

int main()
{
    int a = 0;
    int *p = &a;
    printf("%d\n", p);
}

Quand je lance ce programme, je reçois une valeur différente à chaque fois, ce qui est l'emplacement de mémoire de a, convertie en base 10. Vous pouvez constater que ce genre de chose provoque un avertissement. Vous devriez lire tous les avertissements que votre compilateur vous donne, et ne les ignorer si vous êtes complètement vous vous faites ce que vous voulez.

Autres conseils

Dans le premier programme, b=-5; attribue 251 à b. (Les conversions en un type non signé toujours réduire la valeur modulo un plus la valeur maximale du type de destination.)

Dans le second programme, b=5; attribue simplement 5 à b, puis effectue c = (a - b); la soustraction 0-5 en tant que type int en raison des promotions par défaut - simplement, types "plus petit que int" sont toujours promu int avant d'être utilisées comme opérandes d'opérateurs arithmétiques et binaires.

Modifier Une chose m'a manqué: Depuis c est de type unsigned int, le résultat -5 dans le deuxième programme sera converti en unsigned int lorsque l'affectation à c est effectuée, ce qui UINT_MAX-4. C'est ce que vous voyez avec le prescripteur de %x à printf. Lors de l'impression c avec %d, vous obtenez un comportement non défini, car %d attend un (signé) argument int et vous avez passé un argument unsigned int avec une valeur qui n'est pas représentable dans int ordinaire (signé).

Vous utilisez le spécificateur de format %d. Qui traite l'argument comme un nombre décimal signé (essentiellement de int).

Vous obtenez 251 du premier programme parce que (unsigned char)-5 est 251 alors vous imprimer comme un chiffre décimal signé. Il est promu à 4 octets au lieu de 1, et ces bits sont 0, de sorte que les regards numéro comme 0000...251 (où le 251 est binaire, je viens de le convertir ne).

Vous obtenez -5 à partir du deuxième programme, car (unsigned int)-5 est une grande valeur, mais casté à un int, il est -5. Il se fait traiter comme un entier en raison de la façon dont vous utilisez printf.

Utilisez pour imprimer des valeurs décimales non signées le spécificateur de format de %ud.

Ce que vous voyez est le résultat de comment la machine sous-jacente représente le nombre de la bibliothèque C norme définit signés à des conversions de type non signé (pour l'arithmétique) et la façon dont la machine sous-jacente représente le nombre (pour le résultat du comportement non défini à la fin).

Quand je l'origine écrit ma réponse que j'avais supposé que la norme C ne définit pas explicitement comment les valeurs signées doivent être converties en valeurs non signées, puisque la norme ne définit pas comment les valeurs signées doivent être représentés ou comment convertir les valeurs non signées à des valeurs signées lorsque la plage est en dehors de celle du type signé .

Cependant, il se trouve que la norme ne définit explicitement que lors de la conversion de négatif signé à des valeurs non signées positives. Dans le cas d'un nombre entier, une valeur négative signé x sera converti en UINT_MAX + 1-x, comme si elle était stockée en tant que valeur signée en complément à deux, puis interprété comme une valeur non signée.

Alors, quand vous dites:

unsigned char  a;
unsigned char  b;
unsigned int c;

a = 0; 
b = -5;
c = a + b;

La valeur de b est 251, car -5 est converti en un type non signé de valeur UCHAR_MAX-5 + 1 (255-5 + 1) en utilisant la norme C. Il est alors après cette conversion que l'addition a lieu. Cela fait a + b la même chose que 0 + 251, qui est ensuite stocké dans c. Cependant, quand vous dites:

unsigned char  a;
unsigned char  b;
unsigned int c;

a = 0;
b = 5;
c = (a-b);

printf("c dec: %d\n", c);

Dans ce cas, a et b sont promus ints non signés, pour correspondre avec c, donc ils restent 0 et 5 en valeur. Cependant 0-5 dans les dérivations mathématiques entier non signé à une erreur sousverse, qui est définie pour entraîner UINT_MAX + 1-5. Si cela était arrivé avant la promotion, la valeur serait UCHAR_MAX + 1-5 (à savoir 251 fois).

Cependant, la raison pour laquelle vous voyez -5 imprimé dans la sortie est une combinaison du fait que l'entier non signé UINT_MAX-4 et -5 ont la même représentation binaire exacte, comme -5 et 251 faire avec un seul octet type de données, et le fait que lorsque vous avez utilisé « % d » comme la chaîne mise en forme, que printf dit d'interpréter la valeur de c comme un entier signé au lieu d'un entier non signé.

Depuis une conversion des valeurs non signées à des valeurs signées pour des valeurs non valides n'est pas défini, le résultat devient spécifique de mise en œuvre. Dans votre cas, puisque la machine sous-jacente utilise complément à deux pour les valeurs signées, le résultat est que la valeur non signée UINT_MAX-4 devient la valeur signée -5.

La seule raison pour laquelle cela ne se produit pas dans le premier programme parce qu'un unsigned int et un int signé peut représenter à la fois 251, de sorte que la conversion entre les deux est bien défini et en utilisant « % d » ou « % u » ne matière. Dans le second programme, cependant, il se traduit par un comportement non défini et devient spécifique de mise en œuvre depuis votre valeur de UINT_MAX-4 a en dehors de la portée d'un int signé.

Qu'est-ce qui se passe sous le capot

Il est toujours bon à vérifier ce que vous pensez qu'il se passe ou ce qui devrait se produire avec ce qui se passe réellement, alors regardons la sortie de langage assembleur du compilateur maintenant pour voir exactement ce qui se passe. Voici la partie significative du premier programme:

    mov     BYTE PTR [rbp-1], 0   ; a becomes 0
    mov     BYTE PTR [rbp-2], -5  ; b becomes -5, which as an unsigned char is also 251
    movzx   edx, BYTE PTR [rbp-1] ; promote a by zero-extending to an unsigned int, which is now 0
    movzx   eax, BYTE PTR [rbp-2] ; promote b by zero-extending to an unsigned int which is now 251
    add     eax, edx  ; add a and b, that is, 0 and 251

Notez que même si nous enregistrons une valeur signée de -5 dans l'octet b, lorsque le compilateur favorise, il favorise par zéro l'extension du nombre, ce qui signifie qu'il est interprété comme la valeur non signée que 11.111.011 représente au lieu de la signature valeur. Ensuite, les valeurs promues sont additionnées pour devenir c. Ceci est également la raison pour laquelle les C norme définit signé à des conversions non signés comme il le fait -. Il est facile de mettre en œuvre les conversions sur des architectures qui utilisent le complément à deux pour les valeurs signées

avec le programme 2:

    mov     BYTE PTR [rbp-1], 0 ; a = 0
    mov     BYTE PTR [rbp-2], 5 ; b = 5
    movzx   edx, BYTE PTR [rbp-1] ; a is promoted to 32-bit integer with value 0
    movzx   eax, BYTE PTR [rbp-2] ; b is promoted to a 32-bit integer with value 5
    mov     ecx, edx 
    sub     ecx, eax ; a - b is now done as 32-bit integers resulting in -5, which is '4294967291' when interpreted as unsigned

Nous voyons que a et b sont une nouvelle fois avant que l'arithmétique promu, donc on finit par soustraire deux ints non signés, ce qui conduit à un UINT_MAX-4 en raison de soupassement, qui est aussi -5 comme une valeur signée. Donc, si vous l'interprétez comme une soustraction signé ou non, en raisonà la machine à l'aide de deux formulaire de complément, le résultat correspond à la norme C sans conversions supplémentaires.

L'attribution d'un nombre négatif à une variable non signée viole fondamentalement les règles. Ce que vous faites est de convertir le nombre de négatif à un grand nombre positif. Vous n'êtes même pas garanti, techniquement, que la conversion est le même d'un processeur à l'autre -. Sur le système du complément d'un 1 (le cas échéant existait encore), vous obtiendrez une valeur différente, par exemple

Ainsi, vous obtenez ce que vous obtenez. Vous ne pouvez pas attendre l'algèbre signé pour appliquer encore.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top