web-dev-qa-db-fra.com

Pourquoi le plus petit int, −2147483648, a-t-il le type 'long'?

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?

MODIFIER:

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?

163
Arthur Thévenot

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);
225
fuz

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 ints 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 ints 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 ints 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 int0x80000000 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) ints; 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").

59
rici