Stockage des données dans l'ordinateur - [langage C]

Dans le blog précédent, nous avons appris les types de données du langage C. Passons d'abord en revue les types de données en langage C.


Table des matières

Types intégrés de base du langage C

Classification de base des types

Stockage d'entiers en mémoire

Code d'origine, code inverse, code complémentaire

endianness dans le stockage

pratique 

 Stockage des types à virgule flottante en mémoire

 Règles de stockage des nombres à virgule flottante

Analyse approfondie des problèmes de citation 


Types intégrés de base du langage C

char //Type de données caractère

short // type de données entier court

entier //entier    

long // entier long

long long//long entier long

float //type à virgule flottante simple précision

double // type virgule flottante double précision

int main(void)
{
	printf("%d\n", sizeof(char));
	printf("%d\n", sizeof(short));
	printf("%d\n", sizeof(int));
	printf("%d\n", sizeof(long));
	printf("%d\n", sizeof(long long));
	printf("%d\n", sizeof(float));
	printf("%d\n", sizeof(double));

	return 0;
}

 Nous pouvons également avoir une compréhension générale des valeurs maximales et minimales des types intégrés :

Nous connaissons déjà ces types et tailles de données intégrés de base. Ensuite, nous allons classer grossièrement les types de données en langage C.


Classification de base des types

 Famille d'entiers :

carboniser

        caractère signé

        caractère non signé

court

        signé court [int]

        court non signé [entier]

entier

        signé en entier

        entier non signé

long

        signé long [int]

        non signé long [entier]

Parlons de char en détail. Char est divisé en char signé et char non signé, qui sont respectivement des caractères signés et des caractères non signés. La taille de char est de 8 bits, il y a donc 8 bits binaires. Lorsqu'il s'agit de char signé, le bit le plus élevé est un bit de signe, lorsqu'il s'agit d'un caractère non signé, ce sont tous des bits numériques. Par conséquent, les caractères signés et les caractères non signés représentent des plages différentes, et les résultats sont les suivants :

 Lorsque le nombre est supérieur à la plage pouvant être représentée, le nombre revient au début et continue le cycle.

Int, short, long peuvent faire référence au contenu ci-dessus.

Famille virgule flottante :

 flotter

double

Famille de pointeurs :

entier *p ;

caractère *p ;

flottant *p ;

vide *p ;

......

 Type de construction :

Type de tableau : int arr[10]

Type de structure : structure

Type de syndicat: syndicat

Type d'énumération : enum

Type vide : void signifie type vide (pas de type)

Généralement la valeur de retour de la fonction, le paramètre de la fonction, le type de pointeur.

Ce qui précède est notre classification et notre tri des types de données dans l'ensemble du langage C. Ensuite, apprenons un peu de choses sous-jacentes pour approfondir notre connaissance et notre compréhension des données !


Stockage d'entiers en mémoire

Comme je l'ai écrit dans le blog précédent, une variable doit ouvrir un espace mémoire lorsqu'elle est créée, et la taille de l'espace est liée au type de données.

Comment cette variable entière est-elle stockée dans l'ordinateur ?

Lorsque nous créons un int a = 10, l'ordinateur va ouvrir un espace de 4 octets pour y stocker les données, et les stocker en ordre binaire (l'affichage dans VS est en hexadécimal), soit 32 Une combinaison de 0 ou 1 est stocké. 

Code d'origine, code inverse, code complémentaire

Il existe trois méthodes de représentation binaire des nombres entiers dans les ordinateurs, à savoir le code d'origine, le code complémentaire et le code complémentaire. Les trois méthodes de représentation ont toutes deux parties, un bit de signe et un bit de valeur.Le bit de signe utilise 0 pour représenter "positif" et 1 pour représenter "négatif". Il existe trois manières différentes de représenter les entiers négatifs.

Code original : Le code original peut être obtenu en traduisant directement la valeur en binaire sous forme de nombres positifs et négatifs.

Code inverse : conservez le bit de signe du code d'origine inchangé et inversez le reste du code bit par bit.

Complément : ajoutez +1 au complément résultant. 

Pour les données entières, l'ordinateur les stocke généralement sous forme de complément à deux : car dans le système informatique, toutes les valeurs sont 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 de valeur peuvent être traités de manière uniforme ; en même temps, l'addition et la soustraction peuvent également être traitées de manière uniforme (la CPU n'a qu'un additionneur). De plus, le processus d'opération du code complémentaire et du code d'origine est le même, aucun circuit matériel supplémentaire n'est requis. 

Jetons un coup d'œil au code d'origine, au code inverse et au code complémentaire de 10 et -10

#include<stdio.h>
int main(void)
{
    int a = 10;
   // 00000000000000000000000000001010-原码、反码、补码
    int b = -10;
   // 10000000000000000000000000001010—原码
   // 11111111111111111111111111110101-反码
   // 11111111111111111111111111110110-补码
    return 0;
}

Par rapport au stockage dans l'ordinateur, nous pouvons constater que l'ordre de stockage est un peu erroné, alors pourquoi ? 

 


endianness dans le stockage

Qu'est-ce que le gros et le petit endian ?

Le mode big-endian (stockage) signifie que les bits de poids faible des données sont stockés dans les adresses hautes de la mémoire, tandis que les bits de poids fort des données sont stockés dans les adresses basses de la mémoire.

Le mode little endian (stockage) signifie que les bits de poids faible des données sont stockés dans les adresses basses de la mémoire, tandis que les bits de poids fort des données sont stockés dans les adresses hautes de la mémoire. 

Pourquoi devons-nous faire la distinction entre le stockage big et small endian ?

En effet, dans le système informatique, nous utilisons des octets comme unité, et chaque unité d'adresse correspond à un octet, et un octet est de 8 bits. Mais dans le langage C, en plus du caractère 8 bits, il existe également des types courts 16 bits et des types longs 32 bits (selon le compilateur spécifique).De plus, pour les processeurs de plus de 8 bits, tels que 16 bits Ou pour un processeur 32 bits, puisque la largeur du registre est supérieure à un octet, il doit y avoir un problème d'organisation de plusieurs octets. Par conséquent, cela conduit au mode de stockage big-endian et au mode de stockage little-endian. Par exemple : un court de 16 bits de type x, l'adresse en mémoire est 0x0010, la valeur de x est 0x1122, puis 0x11 est l'octet de poids fort, et 0x22 est l'octet de poids faible. Pour le mode big-endian, mettez 0x11 dans l'adresse basse, c'est-à-dire 0x0010, et mettez 0x22 dans l'adresse haute, c'est-à-dire 0x0011. Le mode little endian, c'est tout le contraire. Notre structure X86 couramment utilisée est en mode petit-boutiste, tandis que KEIL C51 est en mode gros-boutiste. De nombreux ARM et DSP sont en mode little-endian. Certains processeurs ARM peuvent également choisir le mode big-endian ou le mode little-endian par matériel.

Comment faire la distinction entre l'utilisation de programmes pour distinguer si notre ordinateur est big-endian ou little-endian ?

Nous pouvons utiliser la différence entre le gros et le petit boutien pour démarrer le problème. La méthode de stockage du gros boutien et du petit boutien est juste opposée. Nous pouvons définir une valeur fixe de 1, puis utiliser le pointeur pour accéder à l'emplacement de l'adresse basse à imprimer. Si le Si le nombre est 1, il s'agit d'un stockage petit-boutiste, sinon c'est un stockage gros-boutiste. (La variable créée est int, à ce stade, nous n'avons besoin que d'accéder au contenu d'un octet et d'utiliser la conversion de type obligatoire pour changer l'adresse de la variable en (char *)). La théorie est formée, la pratique commence !

#include<stdio.h>
int main(void)

int check_sys(int a)
{
	return (*(char*)&a);
}

int main(void)
{
	int i = 1;
	if (check_sys(i))
	{
		printf("小端\n");
	}
	else
	{
		printf("大端\n");
	}
	return 0;
}

pratique 

 Ce qui précède est le contenu de base du stockage d'entiers, nous pouvons le consolider à travers quelques exercices :

#include <stdio.h>
unsigned char i = 0;
int main()
{
    for(i = 0;i<=255;i++)
   {
        printf("hello world\n");
   }
    return 0;
}

Analysons d'abord cette question : la plage que peut représenter un caractère non signé est (0 ~ 255). Dans la boucle for, si la valeur de i dépasse 255, vous pouvez sortir de la boucle, mais la valeur maximale que je peux représenter est 255. Si vous ajoutez 1 de plus La valeur de i reviendra à 0 pour continuer la boucle, donc ce programme est une boucle sans fin.

#include <stdio.h>
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;
}
//-1的原反补码
//10000000000000000000000000000001-原码
//11111111111111111111111111111110-反码
//11111111111111111111111111111111-补码

Cette question consiste à distinguer la plage de nombres représentée par le caractère, le caractère signé et le caractère non signé. En général, char signifie char signé, donc les valeurs imprimées de a et b doivent être les mêmes, les deux sont -1. Pour les caractères non signés, nous tronquons d'abord le complément de -1 à 11111111, car le format imprimé est %d, donc l'entier non signé de 11111111 est promu à 0000000000000000000000011111111, et le résultat imprimé devrait être 255, donc le résultat imprimé final devrait être - 1, -1, 255.

Nous avons fini de lire le stockage des entiers, alors comment stocker les types à virgule flottante ?


 Stockage des types à virgule flottante en mémoire

Les nombres à virgule flottante sont également des visiteurs fréquents dans notre langage C, et il y a beaucoup de nombres à virgule flottante autour de nous : 3.14, 13.14... Comme mentionné ci-dessus, la famille des nombres à virgule flottante comprend float et double.

La plage de nombres à virgule flottante peut être interrogée dans #include<float.h>.

Nous utilisons un exemple pour introduire le stockage des types à virgule flottante en mémoire :

#include<stdio.h>
int main(void)
{
	int n = 9;
	float* pFloat = (float*) & n;
	printf("n的值为:%d\n", n);
	printf("*pFloat的值为:%f\n", *pFloat);

	*pFloat = 9.0;
	printf("n的值为:%d\n", n);
	printf("*pFloat的值为:%f\n", *pFloat);

	return 0;
}

 Le résultat de sortie est :

L'utilisation d'un pointeur à virgule flottante pour déréférencer un entier lit la valeur différemment que prévu, et inversement l'utilisation d'un nombre à virgule flottante pour extraire de la mémoire ne renvoie pas la valeur attendue. De cela, nous pouvons conclure que les règles de stockage et de lecture des nombres à virgule flottante et des entiers sont différentes. Comment les nombres à virgule flottante doivent-ils être stockés en mémoire ?


 Règles de stockage des nombres à 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 :

(-1)^S * M * 2^E

(-1)^S représente le bit de signe, lorsque S=0, V est un nombre positif ; lorsque S=1, V est un nombre négatif.

M représente un nombre valide, supérieur ou égal à 1 et inférieur à 2.

2^E signifie bits d'exposant.

Par exemple:

5,0 en décimal est 101,0 en binaire, ce qui équivaut à 1,01×2^2. Ensuite, selon le format de V ci-dessus, on peut conclure que S = 0, M = 1,01 et E = 2. La décimale -5.0, écrite en binaire est -101.0, ce qui équivaut à -1.01×2^2. Alors, S=1, M=1.01, E=2.

IEEE 754 stipule : Pour un nombre à virgule flottante de 32 bits, le bit le plus élevé est le bit de signe S, les 8 bits suivants sont l'exposant E et les 23 bits restants sont le nombre effectif M. 

Pour un nombre à virgule flottante de 64 bits, le bit le plus élevé est le bit de signe S, les 11 bits suivants sont l'exposant E et les 52 bits restants sont le significande M.  IEEE 754 a des réglementations spéciales sur le nombre effectif M et l'exposant E :

 Comme mentionné précédemment, 1≤M<2, c'est-à-dire que M peut s'écrire sous la forme 1.xxxxxx, où xxxxxx représente la partie décimale. IEEE 754 stipule que lorsque M est enregistré dans l'ordinateur, le premier chiffre de ce nombre est toujours 1 par défaut, il peut donc être ignoré, et seule la partie xxxxxx suivante est enregistrée. Par exemple, lors de l'enregistrement de 1.01, enregistrez uniquement 01, puis ajoutez le premier 1 lors de la lecture. Le but est d'économiser 1 chiffre significatif. Prenons l'exemple du nombre à virgule flottante 32 bits, il ne reste que 23 bits pour M, et une fois le premier 1 supprimé, cela équivaut à enregistrer 24 chiffres significatifs.

Quant à l'indice E, la situation est plus compliquée :

 Premièrement, E est un entier non signé (unsigned int) , ce qui 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 avoir des nombres négatifs, donc IEEE 754 stipule qu'un nombre intermédiaire doit être ajouté à la valeur réelle de E lorsqu'il est stocké en mémoire. Pour un E à 8 chiffres, le nombre intermédiaire est 127 ; Pour un 11 bits E, ce nombre intermédiaire est 1023. Par exemple, le E de 2^10 est 10, donc lors de son enregistrement en tant que nombre à virgule flottante 32 bits, il doit être enregistré sous 10+127=137, soit 10001001. 

Il existe alors trois situations dans lesquelles E est retiré de la mémoire :

E n'est pas tout 0 ou 1 :

À ce moment, le nombre à virgule flottante est représenté par les règles suivantes, c'est-à-dire que la valeur calculée de l'exposant E est soustraite par 127 (ou 1023) pour obtenir la valeur réelle, puis le premier chiffre 1 est ajouté avant le nombre effectif M.

Par exemple : La forme binaire de 0,25 (1/4) est 0,01. Comme la partie positive doit être 1, c'est-à-dire que la virgule est déplacée vers la droite de 2 bits, elle est 1,0*2^(-2), et son code de commande est de -2 + 127 = 125, exprimé en 01111101, et le mantissa 1.0 supprime la partie entière de 0, et remplit 0 à 23 chiffres 00000000000000000000000, alors sa représentation binaire est: 0011111010000000000000000000000

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 significatif M n'ajoute plus le premier 1, mais restitue à une décimale de 0.xxxxxx. Ceci est fait pour représenter ± 0 et de très petits nombres proches de 0.

E est tout 1 : 

A ce moment, si le nombre significatif M est tout à 0, cela signifie ± l'infini (positif ou négatif dépend du bit de signe s)


Analyse approfondie des problèmes de citation 

Revenons maintenant en arrière et analysons l'exemple précédent :

La valeur binaire de 9 est 000000000000000000000000001001, pourquoi est-elle convertie en un nombre à virgule flottante à neuf queues 0,000000 ?  Puisque l'exposant E est entièrement égal à 0, il répond au deuxième cas de la section précédente. Par conséquent, le nombre à virgule flottante V s'écrit : V=(-1)^0 × 0,0000000000000000001001×2^(-126)=1,001×2^(-146) Évidemment, V est un petit nombre positif proche de 0, donc En notation décimale, c'est 0,000000.

Analysons les problèmes qui surviennent lors de la conversion de nombres à virgule flottante en entiers : 

 L'entier décimal résultant est le résultat de notre programme.

Ce qui précède est tout le contenu des données dans l'ordinateur. J'espère que vous pourrez gagner quelque chose grâce à mon article. Votre soutien est ma plus grande motivation ! ! !

Je suppose que tu aimes

Origine blog.csdn.net/m0_74755811/article/details/131613077
conseillé
Classement