Je pensais que unsigned int ne pouvait stocker que les entiers> = 0 . Mais j'ai essayé d'assigner un négatif à un unsigned int, rien de spécial ne s'est passé .
Alors, quelle est la différence entre Int signé et non signé, et quel est le point si elle peut stocker une valeur de toute façon?
Une déclaration comme
unsigned int t = -1;
printf("%u", t);
est complètement légal et bien défini en C. Les valeurs négatives, lorsqu'elles sont affectées à un type entier non signé, sont converties implicitement (voir par exemple, this projet de norme C en ligne):
6.3.1.3 Entiers signés et non signés
(2) Sinon, si le nouveau type n'est pas signé, la valeur est convertie par ajouter ou soustraire de manière répétée un de plus que la valeur maximale que peut être représenté dans le nouveau type jusqu'à ce que la valeur se situe dans la plage de le nouveau type.
La sortie du programme ci-dessus est une valeur non signée, c'est-à-dire.
4294967295
Vous pouvez donc affecter des valeurs "négatives" à des types intégraux non signés, mais le résultat n'est pas une valeur négative dans son sens réel. Ceci est particulièrement pertinent lorsque vous comparez des valeurs intégrales non signées à des valeurs négatives. Considérons, par exemple, les deux boucles suivantes:
int i = 10;
while (--i >= 0) { // 10 iterations
printf("i: %d\n", i);
}
unsigned int u = 10;
while (--u >= 0) { // endless loop; warning provided.
printf("u: %u\n", u);
}
La première finira après 10 itérations, tandis que la seconde ne finira jamais: les valeurs intégrales non signées ne peuvent pas devenir négatives, donc u >= 0
est toujours vrai.
L'intérêt d'utiliser unsigned int en C est que:
Vous avez raison de dire que unsigned int
ne peut stocker que des entiers> = 0. (Bien entendu, il existe également une limite supérieure, laquelle dépend de votre architecture et est définie par UINT_MAX
dans limits.h).
En affectant une valeur int
signée à un unsigned int
, vous appelez une conversion de type implicite. Le langage C a des règles très précises sur la façon dont cela se produit. Dans la mesure du possible, le compilateur tente de conserver la valeur chaque fois que possible. Prenons ceci par exemple:
int x = 5;
unsigned int y;
y = x;
Le code ci-dessus effectue également une conversion de type, mais comme la valeur "5" est représentable dans les plages entières signées et non signées, la valeur peut être conservée, de sorte que y
aura également une valeur de 5.
Considérons maintenant:
x = -5;
y = x;
Spécifiquement, dans ce cas, vous affectez une valeur qui est pas dans la plage représentable de unsigned int
et, par conséquent, le compilateur doit convertir la valeur en un élément situé dans la plage. La norme C indique que la valeur 1 + UINT_MAX
sera ajoutée à la valeur jusqu'à ce qu'elle se trouve dans la plage de unsigned int
. De nos jours, UINT_MAX
est défini sur 4294967925 (2 ^ 32 - 1) sur la plupart des systèmes. La valeur de y
sera donc réellement 4294967921 (ou 0xFFFFFFFB en hexadécimal).
Il est important de noter que sur les machines à deux complément (presque omniprésentes de nos jours), les représentations binaires d'une valeur signed int
de -5 sont également 0xFFFFFFFB, mais ce n'est pas obligatoire. La norme C autorise et prend en charge les machines qui utilisent différents codages d’entiers. Par conséquent, le code portable ne doit jamais présumer que la représentation binaire sera préservée après une conversion implicite de ce type.
J'espère que cela t'aides!
Un point important est que le débordement d'un entier signé est un comportement indéfini, alors que les entiers non signés sont définis pour être bouclés. En fait, c’est ce qui se passe lorsque vous attribuez une valeur négative à un: cela reste en boucle jusqu’à ce que la valeur soit dans la plage.
Bien que ce comportement englobant des types non signés signifie qu'il est en effet parfaitement correct de leur affecter des valeurs négatives, leur reconversion en types signés n'est pas aussi bien définie (au mieux, il est défini par la mise en œuvre, au pire par le comportement comment vous le faites). Et même s’il est peut-être vrai que sur de nombreuses plates-formes communes, les entiers signé et non signé sont identiques en interne, la signification recherchée de la valeur est importante pour les comparaisons, les conversions (telles que la virgule flottante), ainsi que pour l’optimisation du compilateur.
En résumé, vous devez utiliser un type non signé lorsque vous avez besoin d'une sémantique de bouclage bien définie pour les dépassements et les dépassements insuffisants. Vous devez également représenter des entiers positifs supérieurs au maximum du type signé correspondant (ou du type le plus approprié). Techniquement, vous pouvez éviter les types signés dans la plupart des cas en ajoutant des nombres négatifs au-dessus des types non signés (vous pouvez simplement choisir d'interpréter certains modèles de bits comme des nombres négatifs), mais… pourquoi, quand la langue propose ce service "gratuitement". Le seul vrai problème avec les entiers signés en C est de devoir faire attention au débordement, mais en retour, vous obtiendrez une meilleure optimisation.
Les signes non signés ont 1) des valeurs maximales plus élevées et 2) un débordement enveloppant défini.
Si avec une précision infinie
(unxigned_c = unsigned_a + unsinged_b) >= UINT_MAX
alors unsigned_c
aura réduit modulo UINT_MAX+1
:
#include <limits.h>
#include <stdio.h>
int main()
{
printf("%u\n", UINT_MAX+1); //prints 0
printf("%u\n", UINT_MAX+2); //prints 1
printf("%u\n", UINT_MAX+3); //prints 2
}
Une situation similaire se produit lorsque vous stockez des valeurs signées dans un .. ... Dans ce cas, 6.3.1.3p2 apply - UINT_MAX+1
est ajouté conceptuellement à la valeur).
Par contre, avec les types signés, le dépassement de capacité n’est pas défini. Autrement dit, si vous le permettez, votre programme n’est plus bien formé et la norme ne donne aucune garantie quant à son comportement. Les compilateurs exploitent cela pour l'optimisation en supposant que cela n'arrivera jamais.
Par exemple, si vous compilez
#include <limits.h>
#include <stdio.h>
__attribute__((noinline,noclone)) //or skip the attr & define it in another tu
_Bool a_plus1_gt_b(int a, int b) { return a + 1 > b; }
int main()
{
printf("%d\n", a_plus1_gt_b(INT_MAX,0)); //0
printf("%d\n", INT_MAX+1); //1
}
sur gcc avec -O3
, ça va très probablement imprimer
1
-2147483648