73 votes

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

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 SAME !!!

Pourquoi avons-nous des résultats différents pour les deux ?
Le résultat doit-il être le suivant ?

char est SAME !!! int est le MEME ! !!

A lien vers le codepad .

81voto

Lundin Points 21616

Cela est dû aux diverses règles de conversion implicite de type en C. Il y en a deux qu'un programmeur C doit connaître : les conversions arithmétiques habituelles y el promotions entières (ces derniers font partie des premiers).

Dans le cas du char, vous avez les types (signed char) == (unsigned char) . Ce sont tous deux types de petits nombres entiers . D'autres types de petits entiers de ce type sont bool y short . El règles de promotion des nombres entiers stipulent que chaque fois qu'un petit type d'entier est un opérande d'une opération, son type sera promu en int qui est signé. Cela se produit indépendamment du fait que le type soit signé ou non signé.

Dans le cas de la signed char le signe sera préservé et il sera promu au rang d'objet d'art. int contenant la valeur -5. Dans le cas de l unsigned char il contient la valeur 251 (0xFB ). Il sera promu à un int contenant cette même valeur. Vous obtenez

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

Dans le cas d'un nombre entier, vous avez les types (signed int) == (unsigned int) . Ce ne sont pas des petits types entiers, donc les promotions d'entiers 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 "rang" (taille) mais une signature différente, l'opérande signé est converti au même type que l'opérande non signé. Vous obtenez

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

36voto

zmbq Points 18714

Bonne question !

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

Ah, C favorise implicitement char s à int à diverses occasions. Voici l'une d'entre elles. Votre code dit if(a==b) mais ce que le compilateur transforme en réalité est :

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

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

EDIT : Comme @Carbonic-Acid l'a souligné, (int)b est 251 seulement si a 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 n'a pas une longueur de 8 bits. La seule différence dans ce cas est que (int)b n'est pas 251 mais un autre positif qui n'est pas -5. Ce n'est pas vraiment pertinent pour la question, qui est quand même très cool.

21voto

Nobilis Points 3511

Bienvenue à promotion entière . Si je peux citer le site web :

Si un int peut représenter toutes les valeurs du type original, la valeur est convertie en int ; sinon, elle est convertie en int non signé. C'est ce qu'on appelle les promotions d'entiers. Tous les autres types sont inchangés par les promotions d'entiers.

Le langage C peut être vraiment déroutant lorsque vous effectuez des comparaisons de ce type. J'ai récemment laissé perplexe certains de mes amis programmeurs 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, en effet, imprime This cannot be happening :( et démontre apparemment que 25 est plus petit que -1 !

Ce qui se passe en dessous cependant, c'est que -1 est représenté comme un nombre 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 couchons explicitement le size_t retourné par strlen comme un nombre entier signé :

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

Ensuite, elle comparera 25 contre -1 et tout ira bien pour le monde.

Un bon compilateur devrait vous avertir de la comparaison entre un entier non signé et signé et pourtant, il est toujours si facile de passer à côté (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 qu'a dit James Gosling (l'un des créateurs de Java) avait à dire sur le sujet :

Gosling : Pour moi, en tant que concepteur de langage, ce qui ne compte pas vraiment ces jours-ci, ce que "simple" a vraiment fini par signifier était de pouvoir J'attends de J. Random Developer qu'il tienne la spécification dans sa tête. Cette Cette définition dit que, par exemple, Java n'est pas - et en fait beaucoup de ces ces langages se retrouvent avec beaucoup de cas particuliers, des choses que personne que personne ne comprend vraiment. Interrogez n'importe quel développeur C sur unsigned, et vous découvrirez assez vite et vous découvrirez rapidement que presque aucun développeur C ne comprend ce qui se passe ce qui se passe avec unsigned, ce qu'est l'arithmétique unsigned. Des choses comme ça ont rendu le C complexe. La partie langage de Java est, je pense, assez simple. Les bibliothèques, vous devez les chercher.

10voto

ams Points 10312

La représentation hexagonale de -5 est :

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

Lorsque vous convertissez un nombre signé en un nombre non signé, ou vice versa, le compilateur ne fait ... précisément rien. Qu'y a-t-il à faire ? Soit le nombre est convertible, soit il ne l'est pas, auquel cas il s'ensuit un comportement indéfini ou défini par l'implémentation (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 hexagonale de (unsigned <type>)-5 est :

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

Ça vous dit quelque chose ? Ils sont bit à bit identiques aux versions signées.

Quand vous écrivez if (a == b) , donde a y b sont de type char ce que le compilateur doit réellement lire est if ((int)a == (int)b) . (Il s'agit de la "promotion intégrale" dont tout le monde parle).

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

  • 8 bits signed char en 32 bits signed int : 0xfb -> 0xfffffffb
    • C'est logique, car cela correspond aux représentations de -5 ci-dessus !
    • On l'appelle "extension de signe", car elle copie le bit supérieur de l'octet, le "bit de signe", vers la gauche dans la nouvelle valeur, plus large.
  • 8 bits unsigned char en 32 bits signed int : 0xfb -> 0x000000fb
    • Cette fois, il fait une "extension zéro" parce que le type de source est non signé Il n'y a donc pas de bit de signe à copier.

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

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

1voto

Antonio Points 2246

Ce que je veux dire, c'est que vous n'avez pas reçu un avertissement au moment de la compilation "comparant une expression signée et non signée" ?

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

 unsigned int d = -5;

c'est attribuer une valeur définitivement grande à d, c'est équivalent (même si, probablement pas garanti d'être équivalent) à être :

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

Editar:

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 sûr qu'il n'y aura pas d'erreurs dans la comparaison entre unsigned char y char (lors de 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 de unsigned int y int : lors de la comparaison, l'un des 2 sera converti en un type qui ne peut pas le représenter complètement.

Pour être complet, Je l'ai aussi vérifié pour les courts 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 rapport avec ce sujet, j'ai récemment demandé cette question (pourtant, orienté C++).

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X