Type conversion – non signé à signé int / char

J’ai essayé d’exécuter le programme ci-dessous:

#include  int main() { signed char a = -5; unsigned char b = -5; int c = -5; unsigned int d = -5; if (a == b) printf("\r\n char is SAME!!!"); else printf("\r\n char is DIFF!!!"); if (c == d) printf("\r\n int is SAME!!!"); else printf("\r\n int is DIFF!!!"); return 0; } 

Pour ce programme, je reçois le résultat:

char est DIFF !!! int c’est pareil !!!

Pourquoi obtenons-nous des résultats différents pour les deux?
La sortie doit-elle être comme ci-dessous?

l’omble est le même !!! int c’est pareil !!!

Un lien de pavé de code .

Cela est dû aux différentes règles implicites de conversion de type en C. Un programmeur C doit en connaître deux: les conversions arithmétiques habituelles et les promotions entières (ces dernières font partie de la précédente).

Dans le cas du caractère char, vous avez les types (signed char) == (unsigned char) . Ce sont deux petits types entiers . D’autres petits types entiers sont bool et short . Les règles de promotion des entiers indiquent que chaque fois qu’un petit type d’entier est un opérande d’une opération, son type sera promu dans int , qui est signé. Cela arrivera peu importe si le type a été signé ou non signé.

Dans le cas du caractère signed char , le signe sera conservé et il sera promu dans un int contenant la valeur -5. Dans le cas du caractère unsigned char , il contient une valeur qui est 251 (0xFB). Il sera promu dans un int contenant cette même valeur. Vous vous retrouvez avec

 if( (int)-5 == (int)251 ) 

Dans le cas entier, vous avez les types (signed int) == (unsigned int) . Ce ne sont pas de petits types entiers, donc les promotions entières ne s’appliquent pas. Au lieu de cela, ils sont équilibrés par les conversions arithmétiques habituelles , qui stipulent que si deux opérandes ont le même “rank” (taille) mais une signature différente, l’opérande signé est converti au même type que celui non signé. Vous vous retrouvez avec

 if( (unsigned int)-5 == (unsigned int)-5) 

Cool question!

La comparaison int fonctionne car les deux ints contiennent exactement les mêmes bits, ils sont donc essentiellement les mêmes. Mais qu’en est-il des char ?

Ah, C favorise implicitement les caractères pour les int à diverses occasions. C’est l’un d’eux. Votre code indique if(a==b) , mais ce que le compilateur utilise en réalité est:

 if((int)a==(int)b) 

(int)a est -5, mais (int)b est 251. Celles-ci ne sont certainement pas les mêmes.

EDIT: Comme @ Carbonic-Acid l’a souligné, (int)b est 251 seulement si un caractère est long de 8 bits. Si int est long de 32 bits, (int)b est -32764.

REDIT: Il y a beaucoup de commentaires sur la nature de la réponse si un octet ne fait pas 8 bits. La seule différence dans ce cas est que (int)b n’est pas 251 mais un nombre positif différent, qui n’est pas -5. Ce n’est pas vraiment pertinent pour la question qui est toujours très cool.

Bienvenue dans la promotion entière . Si je peux citer du site:

Si un int peut représenter toutes les valeurs du type d’origine, la valeur est convertie en un int; sinon, il est converti en un int non signé. Celles-ci s’appellent les promotions d’entier. Tous les autres types ne sont pas modifiés par les promotions en nombre entier.

C peut être vraiment déroutant lorsque vous faites des comparaisons comme celles-ci, j’ai récemment insortinggué certains de mes amis non spécialistes de la programmation en C avec la remarque suivante:

 #include  #include  int main() { char* ssortingng = "One looooooooooong ssortingng"; printf("%d\n", strlen(ssortingng)); if (strlen(ssortingng) < -1) printf("This cannot be happening :("); return 0; } 

Ce qui en effet n'imprime pas This cannot be happening :( et démontre apparemment que 25 est plus petit que -1!

Ce qui se passe en dessous est que -1 est représenté comme un entier non signé qui, en raison de la représentation des bits sous-jacents, est égal à 4294967295 sur un système 32 bits. Et naturellement 25 est plus petit que 4294967295.

Si toutefois nous size_t explicitement le type size_t renvoyé par strlen en un entier signé:

 if ((int)(strlen(ssortingng)) < -1) 

Ensuite, il comparera 25 contre -1 et tout ira bien avec le monde.

Un bon compilateur devrait vous avertir de la comparaison entre un entier non signé et un entier signé, mais il est néanmoins si facile à rater (surtout si vous n'activez pas les avertissements).

Cela est particulièrement déroutant pour les programmeurs Java car tous les types primitifs sont signés. Voici ce que James Gosling (l'un des créateurs de Java) avait à dire sur le sujet :

Gosling: Pour moi en tant que concepteur de langage, ce que je ne compte pas vraiment comme ces jours-ci, ce que "simple" a vraiment signifié, était de savoir si J. Random Developer pouvait garder la spécification en tête. Cette définition dit que, par exemple, Java n'est pas - et en fait, beaucoup de ces langages se retrouvent avec beaucoup de cas particuliers, des choses que personne ne comprend vraiment. Quiz tout développeur C sur unsigned, et bientôt vous découvrez que presque aucun développeur C ne comprend réellement ce qui se passe avec unsigned, ce que l'arithmétique non signé est. Des choses comme ça ont rendu C complexe. La partie linguistique de Java est, je pense, assez simple. Les bibliothèques que vous devez rechercher.

La représentation hexadécimale de -5 est:

  • 0xfb signed char 8 bits, complément à deux: 0xfb
  • 32 bits, complément à deux signed int : 0xfffffffb

Lorsque vous convertissez un numéro signé en un nombre non signé, ou inversement, le compilateur ne fait rien. Qu’y a-t-il à faire? Le nombre est soit convertible, soit non, auquel cas le comportement non défini ou défini par l’implémentation suit (je n’ai pas vérifié lequel) et le comportement le plus efficace défini par l’implémentation est de ne rien faire.

Ainsi, la représentation hexadécimale de (unsigned )-5 est:

  • 0xfb unsigned char 8 bits: 0xfb
  • 32 bits, unsigned int : 0xfffffffb

Semble familier? Ils sont bit par bit identiques aux versions signées.

Lorsque vous écrivez if (a == b) , où a et b sont de type char , ce que le compilateur doit réellement lire est if ((int)a == (int)b) . (Ceci est la “promotion d’entier” sur laquelle tout le monde frappe à propos.)

Alors, que se passe-t-il quand on convertit char en int ?

  • 0xfb signed char 8 bits sur signed char sur 32 bits: 0xfb -> 0xfffffffb
    • Eh bien, cela a du sens car il correspond aux représentations de -5 ci-dessus!
    • Il est appelé “sign-extend”, car il copie le bit supérieur de l’octet, le “sign-bit”, vers la nouvelle valeur plus large.
  • 0xfb unsigned char 8 bits à 32 bits signed int : 0xfb -> 0x000000fb
    • Cette fois, il effectue une extension “zéro” car le type de source n’est pas signé , il n’y a donc aucun bit de signe à copier.

Donc, a == b vraiment 0xfffffffb == 0x000000fb => pas de correspondance!

Et, c == d vraiment 0xfffffffb == 0xfffffffb => match!

Mon point est le suivant: n’avez-vous pas reçu un avertissement à la compilation “comparant les expressions signées et non signées”?

Le compilateur essaie de vous informer qu’il a le droit de faire des choses folles! 🙂 J’appendais que des choses folles se produiront en utilisant de grandes valeurs, proches de la capacité du type primitif. Et

  unsigned int d = -5; 

atsortingbue certainement une grande valeur à d, c’est équivalent (même si, probablement pas équivalent à être équivalent) à être:

  unsigned int d = UINT_MAX -4; ///Since -1 is UINT_MAX 

Modifier:

Cependant, il est intéressant de noter que seule la deuxième comparaison donne un avertissement (vérifiez le code) . Donc, cela signifie que le compilateur qui applique les règles de conversion est convaincu qu’il n’y aura pas d’erreurs dans la comparaison entre le caractère unsigned char et le caractère char (lors de la comparaison, ils seront convertis en un type capable de représenter toutes ses valeurs possibles). Et il a raison sur ce point. Ensuite, il vous informe que ce ne sera pas le cas pour unsigned int et int : lors de la comparaison, l’un des 2 sera converti en un type qui ne pourra pas le représenter complètement.

Pour être complet, je l’ai aussi vérifié pour faire court : le compilateur se comporte de la même manière que pour les caractères et, comme prévu, il n’y a pas d’erreurs à l’exécution.

.

En relation avec ce sujet, j’ai récemment posé cette question (encore, orienté C ++).