Le stockage des entiers en mémoire repose sur la représentation binaire. Pour les nombres positifs, le code direct (sign-magnitude), le complément à un et le complément à deux sont identiques. Pour les nombres négatifs, ils diffèrent : le code direct corerspond à la traduction binaire du nombre, le complément à un inverse tous les bits sauf le bit de signe, et le complément à deux ajoute un au complément à un. En pratique, les systèmes informatiques utilisent le complément à deux pour stocker les entiers car il permet de traiter uniformément le bit de signe et la partie numérique, et d'unifier les opérations d'addition et de soustraction. Lors du débogage, la mémoire est souvent affichée en hexadécimal pour plus de lisibilité, et l'ordre de stockage peut être inversé.
L'ordonnancement des octets (endianness) détermine comment les données multi-octets sont stockées en mémoire. Prenons l'exemple d'un entier a = 0x11223344. Dans le stockage gros-boutiste (big-endian), l'octet de poids faible (0x44) est stocké à l'adresse la plus haute, tandis que dans le petit-boutiste (little-endian), il est stocké à l'adresse la plus basse. Cette distinction survient car les processeurs manipulent des registres plus larges qu'un octet, nécessitant une convention pour l'organisation des octets. Par exemple, un short de 16 bits avec la valeur 0x1122 à l'adresse 0x0010 : en gros-boutiste, 0x11 est à 0x0010 et 0x22 à 0x0011 ; en petit-boutiste, l'ordre est inversé. Les architectures courantes comme x86 sont en petit-boutiste, tandis que KEIL C51 est en gros-boutiste ; cretains processeurs ARM permettent même une sélection matérielle.
Pour déterminer l'ordonnancement des octets d'une machine, on peut écrire un programme simple :
int main() {
int valeur = 1;
int *pointeur = &valeur;
if (*((char*)pointeur) == 1) {
printf("Little-endian");
} else {
printf("Big-endian");
}
return 0;
}
Ce code examine le premier octet de l'entier : s'il vaut 1, c'est du petit-boutiste ; sinon, du gros-boutiste. Pour modulariser, on peut définir une fonction :
int verifier_endianness(int *ptr) {
return *((char*)ptr);
}
int main() {
int donnee = 1;
int resultat = verifier_endianness(&donnee);
printf("%d", resultat);
return 0;
}
Le type char occupe un octet (8 bits). En version signée (signed char), le bit de poids fort sert de bit de signe, donnant une plage de valeurs de -128 à 127. En version non signée (unsigned char), tous les bits représentent la magnitude, couvrant 0 à 255. Par exemple, la valeur -1 stockée dans un char signé est tronquée à 0xFF (255 en non signé), mais l'inteprrétation dépend du type lors de l'affichage.
Considérons ce code :
int main() {
char a = -1;
signed char b = -1;
unsigned char c = -1;
printf("a=%d, b=%d, c=%d", a, b, c);
return 0;
}
Ici, -1 est initialement un entier avec le complément à deux sur 32 bits. La troncature à 8 bits donne 0xFF pour chaque variable. Lors de l'impression avec %d, une promotion entière est effectuée : pour a et b (signés), les bits sont étendus avec des 1, résultant en -1 ; pour c (non signé), l'extension se fait avec des 0, donnant 255.
Un autre exemple illustrant la boucle de valeurs :
int main() {
char tableau[1000];
int compteur;
for (compteur = 0; compteur < 1000; compteur++) {
tableau[compteur] = -1 - compteur;
}
printf("%zd", strlen(tableau));
return 0;
}
Dans ce cas, la valeur de tableau[compteur] oscille entre -128 et 127 en raison des limites du type signé, affectant ainsi la longueur calculée par strlen.