introduction
Comme mentionné dans l'article C Language - Binary/Shift Operator/Bit Operator_Study Notes , les données sont stockées sous forme binaire en mémoire, c'est-à-dire 0 et 1 ;
而整数的二进制表示方法有三种,原码、反码和补码,文中也有所提及 而关于浮点数,浮点数在内存中也是存的是二进制,但是相关规则和整数的存储有很大不同
Ce qui suit présentera en détail comment les nombres entiers et les nombres à virgule flottante sont stockés en mémoire.
Stockage d'entiers en mémoire
Il existe trois méthodes de représentation binaire des entiers, à savoir le code original, le code complément et le code complément.
Les trois méthodes de représentation ont des bits de signe et des bits numériques. Le bit de signe utilise 0 pour représenter "positif" et 1 pour représenter "négatif". Le bit le plus élevé du bit numérique est utilisé comme bit de signe, et le reste est constitué de bits numériques.
Les codes original, inverse et complémentaire des entiers positifs sont les mêmes.
Il existe trois manières de représenter des entiers négatifs de différentes manières.
Code original : Le code original est obtenu en traduisant directement la valeur numérique en binaire sous forme de nombres positifs et négatifs.
Code complément à un : Conserver le bit de signe du code d'origine inchangé, et inverser les autres bits petit à petit pour obtenir le code complément à un.
Code complément : le code complément + 1 est le code complément.
Pour les entiers : les données stockées en mémoire stockent en fait le code complémentaire.
Raison :
Dans les systèmes informatiques, les valeurs sont toujours représentées et stockées en complément à deux. La raison en est qu'en utilisant le code complémentaire, le bit de signe et le champ numérique peuvent être traités de manière uniforme ; en même temps, l'addition et la soustraction peuvent également être traitées uniformément (la CPU n'a qu'un additionneur). De plus, le code complémentaire et Le code original est converti l'un en l'autre et le processus de fonctionnement est le même. Des circuits matériels supplémentaires sont requis.
Par exemple (en langage C, dans l'environnement VS2020, le type int et les représentations binaires associées sont les suivantes)
Pour les entiers : les données stockées en mémoire stockent en fait le code complémentaire.
Est-ce le cas ? Vérifions dans le compilateur (environnement VS2022, X64)
en exécutant le code suivant, ouvrons la fenêtre de débogage et voyons que les
données de la fenêtre mémoire sont stockées en hexadécimal dans la mémoire sous cet environnement de compilateur. ,
le résultat de convertir le complément à 6 ci-dessus en hexadécimal est
Le résultat semble être exactement à l’opposé de ce qu’indique le résultat courant. Pourquoi ?
Cela implique un point de connaissance sur l'endianité, lisons la suite
↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ ↓↓↓↓↓ ↓↓↓↓ ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
Endianité
Qu'est-ce que l'endianité
En fait, lorsque plus d'un octet de données est stocké en mémoire, il y a un problème d'ordre de stockage. Selon différents ordres de stockage, nous sommes divisés en stockage big-endian et stockage small-endian. Voici les concepts spécifiques :
- Mode big-endian (stockage) : signifie que le contenu des octets de poids faible des données est stocké à l'adresse haute de la mémoire et que le contenu des octets de poids fort des données est stocké à l'adresse basse de la mémoire.
- Mode Little Endian (stockage) : signifie que le contenu des octets de poids faible des données est stocké à l'adresse basse de la mémoire et que le contenu des octets de poids fort des données est stocké à l'adresse haute de la mémoire.
Pourquoi y a-t-il des grands et des petits bouts ?
En effet, dans les systèmes informatiques, nous utilisons les octets comme unités. Chaque unité d'adresse correspond à un octet et un octet est constitué de 8 bits. Cependant, dans le langage C, en plus du caractère de 8 bits, il existe également 16 Type court -bit et type long 32 bits (selon le compilateur spécifique). De plus, pour les processeurs de plus de 8 bits, tels que les processeurs 16 bits ou 32 bits, puisque la largeur du registre est supérieure à un octet, Ensuite, il doit y avoir un problème de savoir comment organiser plusieurs octets. Cela conduit au mode de stockage big-endian et au mode de stockage small-endian.
Par exemple:
- Un type X court de 16 bits, l'adresse dans la mémoire est 0x0010 et la valeur de
- Pour le mode big-endian, mettez 0x11 dans l'adresse basse, c'est-à-dire 0x0010, et 0x22 dans l'adresse haute, c'est-à-dire 0x0011.
- Le mode Little Endian est tout le contraire.
Notre structure X86 couramment utilisée est le mode Little Endian, tandis que KEIL C51 est en mode big-endian. De nombreux ARM et DSP sont en mode petit-boutiste. Certains processeurs ARM peuvent également sélectionner le mode big-endian ou little-endian par matériel.
Comment déterminer le boutisme de la machine actuelle
Nous pouvons concevoir un petit programme pour juger
int check_sys()
{
int a = 1;
return (*(char*)&a);//小端返回1,大端返回0
}
int main()
{
if (check_sys() == 1)
{
printf("小端\n");
}
else
{
printf("大端\n");
}
return 0;
}
Nous définissons d'abord une variable entière a = 1. Nous savons que a est stocké sous forme hexadécimale dans la mémoire sous la forme 00 00 00 01. Par conséquent, nous obtenons l'adresse de a, partons de l'adresse de départ de a et retirons le contenu d'un octet peut être jugé. S'il s'agit d'un petit-boutiste, le résultat est 1 ; s'il s'agit d'un gros-boutiste, le résultat est 0.
Stockage des données à virgule flottante en mémoire
La famille des nombres à virgule flottante comprend : les types float, double, long double.
- Les types float, double et long double occupent généralement respectivement 4 octets, 8 octets et 16 octets d’espace mémoire. Cependant, cette valeur n'est pas fixe et peut varier en fonction des différents systèmes d'exploitation, compilateurs ou architectures matérielles.
Règles de stockage pour les données à virgule flottante
Selon la norme internationale IEEE (Institute of Electrical and Electronics Engineering) 754, tout nombre binaire à virgule flottante V peut être exprimé sous la forme suivante :
Réglementations IEEE754 :
Pour un nombre à virgule flottante de 32 bits , le bit le plus élevé stocke le bit de signe S, les 8 bits suivants stockent l'exposant E et les 23 bits restants stockent le chiffre significatif M.
Pour un nombre à virgule flottante de 64 bits , le bit le plus élevé stocke le bit de signe S, les 11 bits suivants stockent l'exposant E et les 52 bits restants stockent le chiffre significatif M.
IEEE 754 comporte également des dispositions spéciales pour le chiffre significatif M et l'exposant E.
- Comme mentionné précédemment, 1<M<2, c'est-à-dire M peut s'écrire sous la forme 1.xxxxxx, où xxxxxx représente la partie décimale.
- IEEE754 stipule que lorsque M est stocké dans l'ordinateur, le premier chiffre de ce numéro est toujours 1 par défaut, il peut donc être supprimé et seules les parties xxxxxx suivantes sont enregistrées. Par exemple, lors de l'enregistrement de 1.01, seul 01 est enregistré, et lors de la lecture, le premier 1 est ajouté. Le but est d’économiser 1 chiffre significatif. En prenant comme exemple un nombre à virgule flottante de 32 bits, il ne reste que 23 bits pour M. Une fois le premier 1 arrondi, 24 chiffres significatifs peuvent être enregistrés.
Quant à l'indice E, la situation est plus compliquée.
Premièrement, E est un entier non signé (unsigned int)
- Cela signifie que si E est de 8 bits, sa plage de valeurs est de 0 à 255 ; si E est de 11 bits, sa plage de valeurs est de 0 à 2047.
- Cependant, nous savons que E en notation scientifique peut être négatif.
- Par conséquent, IEEE 754 stipule que la valeur réelle de E doit être ajoutée à un nombre intermédiaire lorsqu'elle est stockée en mémoire,Pour un E de 8 bits, ce nombre intermédiaire est 127 ; pour un E de 11 bits, ce nombre intermédiaire est de 1023.
- Par exemple, le E de 2^10 est 10, donc lorsqu'il est enregistré sous forme de nombre à virgule flottante de 32 bits, il doit être enregistré sous 10+127=137, soit 10001001.
Par exemple
(Comme suit, définissez une variable de type float f avec une valeur de 5,0)
- flotteur f = 5,0
- flottant f = -5,0
5,0 en décimal équivaut à 101,0 en binaire, ce qui équivaut à 1,01×2^2. Ensuite, selon le format de V ci-dessus , nous pouvons obtenir S=0, M=1,01, E=2.
-5,0 en décimal équivaut à -101,0 écrit en binaire, ce qui équivaut à -1,01×2^2. Alors, S=1, M=1,01, E=2.
-5 change simplement le premier bit en 1, donc je n'entrerai pas dans les détails.
Le processus de récupération de nombres à virgule flottante
La récupération de l'index E de la mémoire peut être divisée en trois situations :
- E n'est pas entièrement nul ou incomplet
A ce moment, le nombre à virgule flottante est représenté par les règles suivantes : soustrayez 127 (ou 1023) de la valeur calculée de l'exposant E pour obtenir la valeur réelle, puis ajoutez le premier 1 avant le chiffre significatif M.
Par exemple : la forme binaire de 0,5 est 0,1. Puisque la partie positive doit être 1, c'est-à-dire que le point décimal est déplacé d'une place vers la droite, il vaut 1,0*2^(-1) et son code d'exposant est -1+127 (valeur moyenne) = 126, exprimé par 01111110, et la mantisse 1.0 supprime la partie entière à 0 et remplit 0 à 23 chiffres
à 1
- E est tout 0
A ce moment, l'exposant E du nombre à virgule flottante est égal à 1-127 (ou 1-1023), qui est la valeur réelle. Le nombre effectif M n'ajoute plus le premier 1, mais se réduit à un nombre décimal de 0. .xXXXxxx. Ceci est fait pour représenter ±0 et de très petits nombres proches de 0.
- E est tout 1
A ce moment, si les chiffres significatifs M sont tous 0, cela signifie ± l'infini (positif ou négatif dépend du bit de signe s).
Exemple d'application pratique
int main()
{
int n = 9;
float* pFloat = (float*)&n;
printf("n的值为:%d\n", n);
printf("*pFloat的值为:%f\n", *pFloat);
*pFloat = 9.0;
printf("num的值为:%d\n", n);
printf("*pFloat的值为:%f\n", *pFloat);//9.0
return 0;
}
Résultats d'exécution :
L'explication est la suivante :