Bonjour,
Voici un tout petit code pour introduire la question :
#include <stdio.h>
#include <stdlib.h>
int main()
{
unsigned char a0 = -1;
unsigned short a1 = -1;
unsigned long a2 = -1;
unsigned long long a3 = -1;
printf( "a0 : %u\n"
"a1 : %u\n"
"a2 : %lu\n"
"a3 : %llu\n", a0, a1, a2, a3);
return 0;
}
Ce code me renvoie le nombre max que peuvent contenir les types des variables a0, a1, etc. Ces conversions là ne sont pas expliquées dans le cours de zeste de savoir… On y explique les conversions d’un entier vers un flottant ou d’autres. En l’occurrence, je me demande quel mécanisme permet cette conversion… Qu’aurait affiché mon code si j’avais mis -2, ou -3 ?
Avant l’exécution j’imaginais ceci : en faisant les choses dans l’ordre le compilateur va : écrire -1 sur un int car nous avons une constante entière, ce qui donne FFFFFFFF si un int est codé sur 4 octets. Puis le compilateur va écrire ce nombre dans un unsigned long long (par exemple) ce qui sera un nombre positif mais certainement pas la valeur maximale du type unsigned long long.
En y réfléchissant davantage, je me dis qu’avec mon raisonnement, la syntaxe : long long b = -1; ne fonctionnerait pas, car cela reviendrait à stocker un nombre positif dans le long long alors qu’on demande clairement un nombre négatif. De plus cela reviendrait à ce que les syntaxes long long b = -1; et : long long b = 0xffffffff; reviennent exactement au même (ce qui n’est pas le cas).
Mais alors cela indique quelque chose de significatif : le tutoriel du site me semble légèrement erroné. La valeur d’une constante entière n’est pas seulement conditionnée par ce qu’elle vaudrait "en soi" mais aussi par ce qui l’entoure. En l’occurrence, pour -1 et 0xffffffff, le compilateur comprend qu’on veut un nombre négatif dans un cas et un positif dans l’autre, pourtant ces deux expressions entières semblent avoir exactement la même valeur.
Pour en rajouter encore, la syntaxe : long long a = 5000000000; (5 milliards) ne fonctionnerait pas si la constante entière 5 milliards était d’abord affectée à un int car le nombre est trop grand pour un int. Cela est en contradiction totale avec le cours qui indique que la constante entière serait affectée à un int par défaut avant que la valeur soit affectée au long long.
D’où ma question : que se passe-t-il "vraiment" lors d’une conversion ? Manifestement le cours commet quelques approximations et j’ai du mal à me mettre les idées au clair seul en faisant des tests. Je ne trouve pas vraiment de réponses ailleurs sur le net…
EDIT : Mon interrogation ne se situe pas au niveau de la représentation des nombres négatifs en mémoire. Je suis bien au fait de la notion de complément à deux. Et je connais la représentation des entiers signés (j’ai eu plusieurs réponses qui cernaient mal ma question, je précise cela pour lever les ambiguïtés)
Pour rajouter encore un exemple à mon soucis, voici un deuxième bout de code :
#include <stdio.h>
#include <stdlib.h>
int main()
{
unsigned long long a1 = -1;
unsigned long long a2 = -1UL;
unsigned long long a3 = -1ULL;
printf( "a1 : %llu\n"
"a2 : %llu\n"
"a3 : %llu\n", a1, a2, a3);
return 0;
}
L’exécution me donne :
a1 : 18446744073709551615
a2 : 4294967295
a3 : 18446744073709551615
Le but de mon post est de comprendre l’origine de ce genre résultat, qui-plus-est en contradiction avec des éléments du cours de C de ce site il me semble
EDIT 2 : [en partie faux] après analyse de la réponse de Aabu, on peut anticiper sur le résultat de mon deuxième code, par un raisonnement que je suppose et qui pourrait contenir un bourde, je le soumets à votre validation et je reste attentif à vos futures remarques :
- Premier cas (a1) : -1 est une constante entière qui est donc (cf. cours) de type int. Pour convertir dans le type unsigned long long, il faut donc ajouter 1, puis ajouter ou retrancher la valeur max du type unsigned long long. On tombe sur la valeur max du type unsigned long long. CQFD.
- Deuxième cas : -1 est encore une constante entière et de type int par défaut. La présence du suffixe UL va réaliser une conversion vers le type unsigned long. Par le même raisonnement que précédemment, on tombe cette fois sur 4 milliards et quelques. Puis on doit affecter cette valeur à a2 qui est un unsigned long long et toujours en vertu de la méthode avancée par Aabu, cette valeur est affectée inchangée à a2.
- Troisième cas : -1 est une constante entière de type int. A cause du suffixe ULL, il y a conversion vers le type unsigned long long, ce qui donne (comme pour le premier cas) : 18446744073709551615. Cette valeur est affectée sans changement à a3 car elle est dans la plage de valeurs acceptées.
EDIT 3 : la réponse de Ache donne un bon aperçu de la complexité du type des constantes que l’on retrouve dans la norme (lien : partie 6.4.4.1 Integer constants de la norme C11 (ou C99)). Le type des constantes entières y est détaillé, on comprend immédiatement pourquoi entre autres, si un int fait 4 octets, la constante 0xFFFFFFFF est un unsigned int, et la constante (de même valeur mathématiquement) 4294967295 est un long.
EDIT 4 : en regroupant toutes les réponses on arrive à l’ensemble règles suivantes (pardonnez l’emploi éventuel de mots mal choisis je ne suis pas encore confirmé) :
- en ce qui concerne un éventuel signe, celui est traité ensuite
- on traite d’abord la partie avec les nombres et le suffixe. Un nombre est soit en octal (commence par un 0) soit en hexa (commence par un 0x) soit en décimal (commence par entre 1 et 9). Suivant que l’on soit en décimal, en octal ou en hexa, et suivant le suffixe, le compilateur va déterminer le type de la constante suivant le tableau donné dans le norme (section 6.4.4.1 - Integer constants). On choisit le premier type possible ayant offrant assez de place pour le contenir (on raisonne uniquement en termes de capacité : on peut mettre 4 milliards dans un long, bien que mathématiquement on ne soit pas dans la "bonne" plage de valeurs offerte par un long signé : dans ce cas on aura un nombre négatif dans le long en question).
- On applique l’éventuel signe en faisant un bête complément à deux.
- Notre constante a maintenant un type, il faut maintenant l’affecter à une variable (par exemple lors de la syntaxe long a = -0x253L, on affecte la valeur à a). Pour l’affectation, on est dans le cas d’une conversion d’un type vers un autre : soit la valeur représentée dans le type source peut être accueillie dans le type cible et la conversion se passe tranquillement, soit ce n’est pas le cas deux cas de figure sont possibles :
- si le type cible est non signé, on ajoute ou retranche 1+[la valeur max représentable du type cible] autant de fois qu’il faut pour tomber dans la plage de valeurs représentables par le type cible, et c’est cette valeur qui sera affectée
- si le type cible est signé : ce cas n’est pas intéressant car il y a bien peu de chances qu’un programmeur utilise un tel comportement sans être en fait en train de faire une erreur.
En espérant avoir été clair, voici deux exemples pour illustrer :
long a = -0xFFFFFFFFL;
ici a
vaut 1
unsigned long long a = -1UL;
ici a
vaut 4294967295