web-dev-qa-db-fra.com

Conversion de type - non signé en int / char signé

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

#include <stdio.h>

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, j'obtiens la sortie:

char est DIFF !!! int est MÊME !!!

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

le char est MÊME !!! int est MÊME !!!

A lien codepad .

73
user2522685

C'est à cause des différentes règles de conversion de type implicite en C. Il y en a deux qu'un programmeur C doit connaître: les conversions arithmétiques habituelles et les promotions entières (ces dernières font partie des premières).

Dans le cas char, vous avez les types (signed char) == (unsigned char). Ce sont les deux petits types entiers . Ces autres petits types d'entiers sont bool et short. Les règles de promotion des entiers indiquent que chaque fois qu'un petit type entier est un opérande d'une opération, son type sera promu en int, qui est signé. Cela se produira, que le type soit signé ou non signé.

Dans le cas du signed char, le signe sera conservé et il sera promu en int contenant la valeur -5. Dans le cas du unsigned char, il contient une valeur qui est 251 (0xFB). Il sera promu en 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 indiquent que si deux opérandes ont le même "rang" (taille) mais une signature différente, le signé l'opérande est converti dans le même type que celui non signé. Vous vous retrouvez avec

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

Cool question!

La comparaison int fonctionne, car les deux entrées contiennent exactement les mêmes bits, elles sont donc essentiellement les mêmes. Mais qu'en est-il des chars?

Ah, C promeut implicitement chars en ints à diverses occasions. C'est l'un d'eux. Votre code dit if(a==b), mais ce que le compilateur convertit en fait:

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

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

EDIT: Comme l'a souligné @ Carbonic-Acid, (int)b vaut 251 uniquement si un char a une longueur de 8 bits. Si int a une longueur de 32 bits, (int)b est -32764.

REDIT: Il y a tout un tas de commentaires discutant de la nature de la réponse si un octet ne fait pas 8 bits de long. 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.

36
zmbq

Bienvenue sur promotion entière . Si je peux citer le site Web:

Si un int peut représenter toutes les valeurs du type d'origine, la valeur est convertie en int; sinon, il est converti en un entier non signé. Celles-ci sont appelées les promotions entières. Tous les autres types sont inchangés par les promotions entières.

C peut être vraiment déroutant lorsque vous faites des comparaisons comme celles-ci, j'ai récemment intrigué certains de mes amis de programmation non-C avec la taquinerie suivante:

#include <stdio.h>
#include <string.h>

int main()
{
    char* string = "One looooooooooong string";

    printf("%d\n", strlen(string));

    if (strlen(string) < -1) printf("This cannot be happening :(");

    return 0;
}

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

Cependant, 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.

Cependant, si nous convertissons explicitement le type size_t Renvoyé par strlen en entier signé:

if ((int)(strlen(string)) < -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 signé et pourtant il est toujours aussi facile de le manquer (surtout si vous n'activez pas les avertissements).

Cela est particulièrement déroutant pour les programmeurs Java car tous les types primitifs y 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, que je ne considère pas vraiment comme ces jours-ci, ce que "simple" a fini par signifier était que je pouvais m'attendre à ce que J. Random Developer tienne la spécification dans sa tête. Cette définition dit que, par exemple, Java ne l'est pas - et en fait, beaucoup de ces langues se retrouvent avec beaucoup de cas d'angle, des choses que personne ne comprend vraiment. Quiz n'importe quel développeur C sur non signé, et très vite vous découvrez que presque aucun développeur C ne comprend réellement ce qui se passe avec non signé, ce qu'est l'arithmétique non signé. 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.

21
Nobilis

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

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

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

Ainsi, la représentation hexadécimale de (unsigned <type>)-5 Est:

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

Semble familier? Ils sont peu à peu les mêmes que les versions signées.

Lorsque vous écrivez if (a == b), où a et b sont de type char, ce que le compilateur doit lire est if ((int)a == (int)b). (Il s'agit de cette "promotion entière" dont tout le monde parle.)

Alors, que se passe-t-il lorsque nous convertissons char en int?

  • 8 $ signed char À 32 bits signed int: 0xfb -> 0xfffffffb
    • Eh bien, cela a du sens car il correspond aux représentations de -5 Ci-dessus!
    • Il est appelé "extension de signe", car il copie le bit supérieur de l'octet, le "bit de signe", vers la gauche dans la nouvelle valeur plus large.
  • 8 $ unsigned char À 32 bits signed int: 0xfb -> 0x000000fb
    • Cette fois, il effectue une "extension nulle" car le type de source est non signé, il n'y a donc pas de bit de signe à copier.

Donc, a == b Ne fait vraiment 0xfffffffb == 0x000000fb => Aucune correspondance!

Et, c == d Correspond vraiment à 0xfffffffb == 0xfffffffb =>!

10
ams

Mon point est le suivant: n'avez-vous pas reçu d'avertissement au moment de la compilation "comparer l'expression signée et l'expression non signée"?

Le compilateur essaie de vous informer qu'il a le droit de faire des trucs fous! :) J'ajouterais que des trucs fous se produiront en utilisant de grandes valeurs, proches de la capacité du type primitif. Et

 unsigned int d = -5;

attribue définitivement une grande valeur à d, c'est équivalent (même si, probablement pas garanti d'être équivalent) pour ê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érifier le code) . Cela signifie donc que le compilateur appliquant les règles de conversion est convaincu qu'il n'y aura pas d'erreurs dans la comparaison entre unsigned char et char (pendant la comparaison, ils seront convertis en un type qui peut représenter en toute sécurité 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 peut pas le représenter complètement.

Par souci d'exhaustivité, je l'ai également vérifié brièvement : le compilateur se comporte de la même manière que pour les caractères, et, comme prévu, il n'y a aucune erreur au moment de l'exécution.

.

Relativement à ce sujet, j'ai récemment demandé cette question (pourtant, orienté C++).

1
Antonio