Pourquoi le complément se comporte différemment via printf?

Je lisais un chapitre sur les opérateurs binarys, je suis tombé sur le programme opérateur de complément 1 et j’ai décidé de l’exécuter sur Visual C ++.

int main () { unsigned char c = 4, d; d = ~c; printf("%d\n", d); } 

Il donne la sortie valide: 251

Alors, au lieu d’utiliser d comme variable pour conserver la valeur de ~c , j’ai décidé d’imprimer directement la valeur de ~c .

 int main () { unsigned char c=4; printf("%d\n", ~c); } 

Il donne la sortie -5 .

Pourquoi ça n’a pas marché?

Dans cette déclaration:

 printf("%d",~c); 

le c est converti en type int 1 avant que l’ opérateur ~ (complément binary) ne soit appliqué. Cela est dû aux promotions en nombre entier , appelées à l’opérande du ~ . Dans ce cas, un object de type unsigned char est promu en (signé) int , qui est ensuite (après une évaluation opérateur) utilisé par la fonction printf , avec un spécificateur de format %d correspondant.

Notez que les promotions d’arguments par défaut (comme printf est une fonction variadic) ne jouent aucun rôle ici, car l’object est déjà de type int .

Par contre, dans ce code:

 unsigned char c = 4, d; d = ~c; printf("%d", d); 

les étapes suivantes se produisent:

  • c fait l’object de promotions entières à cause de ~ (de la même manière, comme décrit ci-dessus)
  • ~c rvalue est évalué comme valeur (signée) int (par exemple -5 )
  • d=~c effectue une conversion implicite de int en caractères unsigned char , car d a un tel type. Vous pouvez penser à lui comme d = (unsigned char) ~c . Notez que d ne peut pas être négatif (c’est la règle générale pour tous les types non signés).
  • printf("%d", d); invoque les promotions d’arguments par défaut , donc d est converti en int et la valeur (non négative) est préservée (c’est-à-dire que le type int peut représenter toutes les valeurs de type unsigned char ).

1) en supposant que int peut représenter toutes les valeurs du caractère unsigned char (voir le commentaire de TC ci-dessous), mais il est très probable que cela se produise de cette manière. Plus précisément, nous supposons que INT_MAX >= UCHAR_MAX est INT_MAX >= UCHAR_MAX . En règle générale, la sizeof(int) > sizeof(unsigned char) et l’octet sont composés de huit bits. Sinon, c serait converti en unsigned int (comme dans le §6.3.1.1 / p2 du § C11), et le spécificateur de format devrait également être modifié en conséquence pour éviter d’avoir une UB (C11 §7.21.6.1 / p9) .

char est promu dans int dans l’instruction printf avant l’opération ~ dans le second extrait. Donc c , qui est

 0000 0100 (2's complement) 

en binary est promu à (en supposant que la machine 32 bits)

 0000 0000 0000 0000 0000 0000 0000 0100 // Say it is x 

et son complément binary est égal au complément à deux de la valeur moins un ( ~x = −x − 1 )

 1111 1111 1111 1111 1111 1111 1111 1011 

qui est -5 en décimal en forme de complément à 2.

Notez que la promotion par défaut de char c à int est également effectuée dans

 d = ~c; 

opération avant complément mais le résultat est reconverti en caractère unsigned char car d est de type unsigned char .

C11: 6.5.16.1 Affectation simple (p2):

Dans l’affectation simple ( = ), la valeur de l’opérande droite est convertie dans le type de l’expression d’affectation et remplace la valeur stockée dans l’object désigné par l’opérande gauche.

et

6.5.16 (p3):

Le type d’une expression d’affectation est le type que l’opérande gauche aurait après la conversion de lvalue.

Pour comprendre le comportement de votre code, vous devez apprendre le concept appelé “Integer Promotions” (implicitement dans votre code avant que le bit ne soit pas une opération sur un opérande unsigned char ) Comme mentionné dans le projet de comité N1570:

§ 6.5.3.3 Opérateurs arithmétiques unaires

  1. Le résultat de l’opérateur ~ est le complément binary de son opérande (c’est-à-dire que chaque bit du résultat est défini si et seulement si le bit correspondant dans l’opérande converti n’est pas défini). Les promotions d’entier sont effectuées sur l’opérande et le résultat est du type promu . Si le type promu est un “type non signé”, l’expression ~E est équivalente à la valeur maximale représentable dans ce type moins E “.

Parce que le type de caractère unsigned char est plus étroit que (car il nécessite moins d’octets) int type, – la promotion implicite de type effectuée par la machine abstraite (compilateur) et la valeur de la variable c est promue dans int au moment de la compilation (avant l’application du complément ~ ). Il est nécessaire pour l’exécution correcte du programme car ~ besoin d’un opérande entier.

§ 6.5 Expressions

  1. Certains opérateurs (l’opérateur unaire ~ et les opérateurs binarys << , >> , & , ^ et | , collectivement décrits comme opérateurs binarys) doivent avoir des opérandes de type entier . Ces opérateurs génèrent des valeurs qui dépendent des représentations internes des nombres entiers et ont des aspects dé fi nis et non définis pour les types signés.

Les compilateurs sont suffisamment intelligents pour parsingr les expressions, vérifier la sémantique des expressions, effectuer des vérifications de type et des conversions arithmétiques si nécessaire. C'est la raison pour laquelle, pour appliquer ~ sur le type de caractère, nous n'avons pas besoin d'écrire explicitement ~(int)c - appelé "casting de type explicite" (et évite les erreurs).

Remarque:

  1. La valeur de c est promue dans int dans l'expression ~c , mais le type de c est toujours un caractère unsigned char - ce n'est pas le cas de son type. Ne soyez pas confus.

  2. Important: le résultat de ~ opération est de type int !, vérifiez ci-dessous le code (je n'ai pas de compilateur vs, j'utilise gcc):

     #include #include int main(void){ unsigned char c = 4; printf(" sizeof(int) = %zu,\n sizeof(unsigned char) = %zu", sizeof(int), sizeof(unsigned char)); printf("\n sizeof(~c) = %zu", sizeof(~c)); printf("\n"); return EXIT_SUCCESS; } 

    comstackz-le et lancez-le:

     $ gcc -std=gnu99 -Wall -pedantic xc -ox $ ./x sizeof(int) = 4, sizeof(unsigned char) = 1 sizeof(~c) = 4 

    Remarque : la taille du résultat de ~c est la même que celle de int , mais pas égale à unsigned char - le résultat de l'opérateur ~ dans cette expression est int ! que comme mentionné 6.5.3.3 Opérateurs arithmétiques unaires

    1. Le résultat de l'opérateur unaire est le négatif de son opérande (promu). Les promotions d'entier sont effectuées sur l'opérande et le résultat est du type promu.

Maintenant, comme @haccks a également expliqué dans sa réponse - que le résultat de ~c sur la machine 32 bits et pour la valeur de c = 4 est:

 1111 1111 1111 1111 1111 1111 1111 1011 

en décimal, c'est -5 - c'est la sortie de votre deuxième code !

Dans votre premier code , une ligne de plus est intéressante à comprendre b = ~c; , parce que b est une variable unsigned char et que le résultat de ~c est de type int , pour adapter la valeur du résultat de ~c à b valeur du résultat (~ c) est tronquée comme suit:

  1111 1111 1111 1111 1111 1111 1111 1011 // -5 & 0xFF & 0000 0000 0000 0000 0000 0000 1111 1111 // - one byte ------------------------------------------- 1111 1011 

L'équivalent décimal de 1111 1011 est 251 . Vous pouvez obtenir le même effet en utilisant:

 printf("\n ~c = %d", ~c & 0xFF); 

ou comme suggéré par @ouah dans sa réponse en utilisant explicitement le casting.

Lorsque vous appliquez l’opérateur ~ à c il est promu dans int , le résultat est également un int .

alors

  • dans le 1er exemple, le résultat est converti en unsigned char signed int puis promu dans signed int et imprimé.
  • dans le 2ème exemple, le résultat est imprimé comme signed int .

Il donne l’op -5. pourquoi ça n’a pas marché?

Au lieu de:

 printf("%d",~c); 

utilisation:

 printf("%d", (unsigned char) ~c); 

pour obtenir le même résultat que dans votre premier exemple.

~ opérande subit une promotion entière et la promotion par défaut des arguments est appliquée aux arguments des fonctions variadiques.

Promotion entière, à partir du standard:

Si le type de l’opérande avec un type entier signé peut représenter toutes les valeurs du type de l’opérande avec un type entier non signé, l’opérande avec un type entier non signé doit être converti dans le type de l’opérande avec un type entier signé.