Pour un projet scolaire, je dois coder la fonction C printf. Les choses se passent plutôt bien, mais il y a une question à laquelle je ne trouve pas de bonne réponse, alors je suis là.
printf("PRINTF(d) \t: %d\n", -2147483648);
me dit (gcc -Werror -Wextra -Wall
):
error: format specifies type 'int' but the argument has type 'long'
[-Werror,-Wformat]
printf("PRINTF(d) \t: %d\n", -2147483648);
~~ ^~~~~~~~~~~
%ld
Mais si j'utilise une variable int, tout se passe bien:
int i;
i = -2147483648;
printf("%d", i);
Pourquoi?
J'ai compris beaucoup de points et ils étaient très intéressants. Quoi qu'il en soit, je suppose que printf
utilise la librairie <stdarg.h>
Et que, par conséquent, va_arg(va_list ap, type)
devrait également renvoyer le bon type. Pour %d
Et %i
, Le type renvoyé est évidemment un int
. Cela change-t-il quelque chose?
En C, -2147483648
n'est pas une constante entière. 2147483648
est une constante entière et -
n'est qu'un opérateur unaire qui lui est appliqué, produisant une expression constante. La valeur de 2147483648
ne rentre pas dans un int
(il est trop grand, 2147483647
est généralement le plus grand nombre) et la constante de type entier a donc le type long
, ce qui est à l'origine du problème observé. Si vous voulez mentionner la limite inférieure pour un int
, utilisez la macro INT_MIN
de <limits.h>
(approche portable) ou évitez soigneusement de mentionner 2147483648
:
printf("PRINTF(d) \t: %d\n", -1 - 2147483647);
Le problème est que -2147483648
n'est pas un littéral entier. C'est une expression composée de l'opérateur de négation unaire -
et de l'entier 2147483648
, trop gros pour être un int
si int
s ont 32 bits. . Étant donné que le compilateur choisira un entier signé de taille appropriée pour représenter 2147483648
avant d'appliquer l'opérateur de négation, le type du résultat sera plus grand que int
.
Si vous savez que vos int
s sont en 32 bits et que vous souhaitez éviter l'avertissement sans altérer la lisibilité, utilisez une conversion explicite:
printf("PRINTF(d) \t: %d\n", (int)(-2147483648));
Ce comportement est défini sur une machine à complément à 2 avec int
s 32 bits.
Pour une portabilité théorique accrue, utilisez INT_MIN
à la place du nombre et indiquez-nous où vous avez trouvé une machine non complémentaire à 2 pour la tester.
Pour être clair, ce dernier paragraphe était en partie une blague. INT_MIN
est définitivement la voie à suivre si vous voulez dire "le plus petit int
", car la taille de int
varie. Il y a encore beaucoup d'implémentations 16 bits, par exemple. Ecrire -231 n'est utile que si vous entendez toujours exactement cette valeur, auquel cas vous utiliseriez probablement un type de taille fixe tel que int32_t
au lieu de int
.
Vous voudrez peut-être une alternative à l'écriture décimale du nombre pour le rendre plus clair pour ceux qui ne remarqueront peut-être pas la différence entre 2147483648
et 2174483648
, mais vous devez faire attention.
Comme mentionné ci-dessus, sur une machine 32 bits à complément 2, (int)(-2147483648)
ne débordera pas et sera donc bien défini, car -2147483648
sera traité comme un type signé plus large. Cependant, il n'en va pas de même pour (int)(-0x80000000)
. 0x80000000
sera traité comme un unsigned int
(puisqu'il 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 du résultat unsigned int
0x80000000
to int
implique un débordement. Pour éviter le débordement, vous devez convertir la constante hexadécimale en un type signé: (int)(-(long long)(0x80000000))
.
De même, vous devez faire attention si vous souhaitez utiliser l'opérateur de décalage à gauche. 1<<31
est un comportement indéfini sur les machines 32 bits avec 32 bits (ou plus petit) int
s; il évaluera seulement à 231 si int
est d'au moins 33 bits, car le décalage à gauche de k
bits n'est bien défini que si k
est strictement inférieur au nombre de bits non signés de l'entier type de l'argument de gauche.
1LL<<31
est sans danger, car long long int
est nécessaire pour pouvoir représenter 263-1, donc sa taille de bit doit être supérieure à 32. Donc, la forme
(int)(-(1LL<<31))
est peut-être le plus lisible. YMMV.
Pour tous les pédants qui passent, cette question est marquée C, et le dernier brouillon C (n1570.pdf) indique, en ce qui concerne E1 << E2
, où E1
a un type signé, que la valeur est défini uniquement si E1
est non négatif et E1 × 2E2
"est représentable dans le type de résultat". (§6.5.7 para 4).
C’est différent du C++, dans lequel l’application de l’opérateur de décalage à gauche est définie si E1
est non négatif et que E1 × 2E2
"est représentable dans le type non signé correspondant type de résultat "(§5.8, paragraphe 2, italiques ajoutés).
En C++, selon le projet de norme le plus récent, la conversion d'une valeur entière en un type entier signé est définie 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 par. 3 - indique que "le résultat est défini par la mise en oeuvre ou un signal défini par la mise en oeuvre est émis").