Le problème est que -2147483648
n'est pas un entier littéral. Il s'agit d'une expression composée de l'opérateur de négation unaire -
et le nombre entier 2147483648
qui est trop grand pour être un int
si int
sont de 32 bits. Puisque le compilateur choisira un entier signé de taille appropriée pour représenter les 2147483648
avant d'appliquer l'opérateur de négation, le type du résultat sera plus grand qu'un int
.
Si vous savez que votre int
sont de 32 bits et que vous voulez éviter l'avertissement sans nuire à la lisibilité, utilisez une conversion explicite :
printf("PRINTF(d) \t: %d\n", (int)(-2147483648));
Il s'agit d'un comportement défini sur une machine à complément à 2 avec une mémoire de 32 bits. int
s.
Pour une portabilité théorique accrue, utilisez INT_MIN
à la place du numéro, et faites-nous savoir où vous avez trouvé une machine sans complément à 2 pour le tester.
Pour être clair, ce dernier paragraphe était en partie une blague. INT_MIN
est certainement la voie à suivre si vous voulez dire "le plus petit". int
", car int
varie en taille. Il existe encore beaucoup d'implémentations 16 bits, par exemple. Écriture de -2 31 n'est utile que si vous voulez absolument toujours dire exactement cette valeur, auquel cas vous utiliserez probablement un type de taille fixe comme int32_t
au lieu de int
.
Vous pourriez souhaiter une alternative à l'écriture du nombre en décimal afin de rendre les choses plus claires pour ceux qui ne remarqueraient pas la différence entre 2147483648
y 2174483648
mais il faut être prudent.
Comme indiqué ci-dessus, sur une machine 32 bits à complément à 2, (int)(-2147483648)
ne débordera pas et est donc bien définie, car -2147483648
sera traité comme un type de signature plus large. Cependant, il n'en va pas de même pour les (int)(-0x80000000)
. 0x80000000
sera traité comme un unsigned int
(puisqu'elle s'inscrit dans la représentation non signée) ; -0x80000000
est bien défini (mais le -
n'a pas d'effet si int
est de 32 bits), et la conversion de la valeur du unsigned int
0x80000000
a int
implique un débordement. Pour éviter ce débordement, vous devez convertir la constante hexagonale en un type signé : (int)(-(long long)(0x80000000))
.
De même, vous devez faire attention si vous voulez utiliser l'opérateur de décalage vers la gauche. 1<<31
est un comportement indéfini sur les machines 32 bits avec des logiciels 32 bits (ou plus petits). int
il ne sera évalué qu'à 2 31 si int
est d'au moins 33 bits, car le décalage vers la gauche de k
n'est bien définie que si k
est strictement inférieur au nombre de bits de non signe du type entier de l'argument de gauche.
1LL<<31
est sûr, puisque long long int
est nécessaire pour pouvoir représenter 2 63 -1, de sorte que la taille de ses bits doit être supérieure à 32. La forme
(int)(-(1LL<<31))
est sans doute le plus lisible. YMMV.
Pour les pédants de passage, cette question est étiquetée C, et le dernier projet C (n1570.pdf) dit, en ce qui concerne E1 << E2
où E1
a un type signé, que la valeur n'est définie que si E1
est non négatif et E1 × 2E2
"est représentable dans le type de résultat". (§6.5.7 paragraphe 4).
C'est différent du C++, dans lequel l'application de l'opérateur de décalage vers la gauche est définie si E1
est non négatif et E1 × 2E2
"est représentable dans le type non signé correspondant du type de résultat" (§5.8 par. 2, souligné par l'auteur).
En C++, selon le projet de norme le plus récent, la conversion d'une valeur entière en un type d'entier signé est la suivante défini par la mise en œuvre si la valeur ne peut pas être représentée dans le type de destination (§4.7 par. 3). Le paragraphe correspondant de la norme C -- §6.3.1.3 para. 3 -- dit que "soit le résultat est défini par l'implémentation, soit un signal défini par l'implémentation est levé").
0 votes
A votre question complémentaire à laquelle j'ai déjà répondu, mais mon commentaire s'y rapportant a été supprimé :
va_arg()
ne sait pas quel est le type de l'argument que vous essayez de récupérer. Vous devez le savoir et si vous essayez de récupérer un type différent de celui qui a été passé en tant qu'argument, il s'agit d'un comportement non défini. Ceci s'applique également si vous faitesprintf("%d\n", -2147483648)
car l'argument est de typelong
maisprintf
tente de récupérer unint
.0 votes
Pas de doublon. Si vous avez lu la réponse acceptée pour l'autre question, cela est dû à un comportement non défini spécifique au contexte de la question. Cette question ne porte pas sur un comportement indéfini. Cette question porte sur le langage C et l'autre sur le langage C++. Les deux langages ont des règles similaires pour la promotion, mais il peut y avoir des différences subtiles. Cette question aidera davantage de futurs visiteurs, et peut-être déjà si l'on en croit le nombre de votes beaucoup plus élevé.
0 votes
Il ne s'agit pas non plus d'un doublon de la deuxième question. Le fait essentiel est qu'ils ont spécifié le littéral en hexadécimal, ce qui signifie qu'il s'agit d'un int non signé plutôt que d'un long signé.
1 votes
@AdrianMcCarthy ils ont spécifié le littéral en hexadécimal, ce qui signifie qu'il s'agit d'un int non signé plutôt que d'un long signé. Cela peut être lu comme "les constantes hexagonales sont toujours non signées". Les anciens compilateurs C pré-normes rendaient souvent les constantes hexadécimales non signées, ce qui peut prêter à confusion. Par 6.4.4.1 Constantes entières paragraphe 5 : "Le type d'une constante entière est le premier de la liste correspondante dans lequel sa valeur peut être représentée". Dans ce cas,
0x80000000
est ununsigned int
ici parce qu'il s'inscrit dans ununsigned int
mais il est trop grand pour[signed] int
.0 votes
@AdrianMcCarthy J'ai réécrit mon commentaire pour préciser que j'essayais juste de clarifier, et j'ai supprimé mon commentaire précédent.