165 votes

Pourquoi le plus petit int, 2147483648, est-il de 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 n'arrive pas à trouver une bonne réponse, alors me voici.

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 ?

EDITAR:

J'ai compris de nombreux points et ils étaient très intéressants. Quoi qu'il en soit, je pense que printf utilise le <stdarg.h> la liberté et ainsi de suite, va_arg(va_list ap, type) doit également renvoyer le bon type. Dans le cas d'un %d y %i le type retourné est évidemment un int . Cela change-t-il quelque chose ?

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 faites printf("%d\n", -2147483648) car l'argument est de type long mais printf tente de récupérer un int .

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é.

232voto

FUZxxl Points 21462

En C, -2147483648 n'est pas une constante entière. 2147483648 est une constante entière, et - est simplement un opérateur unaire qui lui est appliqué, ce qui donne une expression constante. La valeur de 2147483648 n'entre pas dans un int (il est un peu trop grand, 2147483647 est typiquement le plus grand entier) et donc la constante entière est de type long qui est à l'origine du problème que vous observez. Si vous voulez mentionner la limite inférieure d'un int soit utiliser la macro INT_MIN de <limits.h> (l'approche portable) ou éviter soigneusement de mentionner les 2147483648 :

printf("PRINTF(d) \t: %d\n", -1 - 2147483647);

60voto

rici Points 45980

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 << E2E1 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é").

2 votes

La manière conventionnelle de définir INT_MIN es #define INT_MIN (-(INT_MAX)-1) (ce qui évite le problème d'essayer de prendre le négatif d'un long que vous avez décrit). (référence)

2 votes

@abelenky : Je sais que c'est comme ça INT_MIN est définie de manière conventionnelle sur des machines à complément 2s . Sinon, je suppose qu'il serait logique de le définir comme suit (-INT_MAX) parce qu'il est logique de ne taper le nombre 2147483647 qu'une seule fois. Mais il a été (raisonnablement) suggéré qu'appeler printf avec -2147483647-1 est un peu bizarre. Bien sûr, maintenant que nous avons eu cette discussion, nous savons pourquoi vous devez faire cela. J'ai seulement suggéré que l'utilisation d'un cast explicite vous permet d'écrire le nombre littéral dans la forme où il serait normalement écrit par ceux qui ne sont ni des compilateurs ni des avocats de la langue :)

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X