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 .
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)
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 char
s?
Ah, C promeut implicitement char
s en int
s à 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.
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.
La représentation hexadécimale de -5
Est:
signed char
: 0xfb
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:
unsigned char
: 0xfb
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
?
signed char
À 32 bits signed int
: 0xfb
-> 0xfffffffb
-5
Ci-dessus!unsigned char
À 32 bits signed int
: 0xfb
-> 0x000000fb
Donc, a == b
Ne fait vraiment 0xfffffffb == 0x000000fb
=> Aucune correspondance!
Et, c == d
Correspond vraiment à 0xfffffffb == 0xfffffffb
=>!
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++).