Langage C avancé : analyse approfondie du stockage des données en mémoire

contenu

1. Introduction aux types de données

1.1 Classification de base des types

2. Stockage des formes en mémoire

2.1 Code original, code inverse, code complément

2.2 Introduction au Big et Little Endian

2.3 Exercices

3. Stockage en virgule flottante en mémoire

3.1 Un exemple de stockage en virgule flottante : 

3.2 Règles de stockage en virgule flottante


1. Introduction aux types de données

Nous avons découvert les types intégrés de base et la quantité de stockage qu'ils occupent

char //Type de données caractère, occupant 1 octet

short //Entier court, occupant 2 octets

int //forme, occupe 4 octets

long //Entier long, occupant 4 octets

long long //Entier plus long, occupant 8 octets

float // Nombre à virgule flottante simple précision, occupant 4 octets

double // Nombre à virgule flottante double précision, occupant 8 octets

Signification du type :

1. Utilisez ce type pour ouvrir la taille de l'espace mémoire (la taille détermine la plage d'utilisation).

2. Comment regarder la perspective de l'espace mémoire. (Par exemple, int et float sont tous deux de 4 octets, l'un est un entier et l'autre est un décimal quand on le regarde)

1.1 Classification de base des types

Famille d'entiers :

carboniser

        caractère non signé

        caractère signé

court

        [int] court non signé, [int] peut être omis

        signé court [int], [int] peut être omis

entier

        entier non signé

        signé en entier

long

        unsigned long [int], [int] peut être omis

        signé long [int], [int] peut être omis

int mian()
{
	char c = 'w'; //char到底是signed char 还是unsigned char是不确定的,取决于编译器的实现
	signed char c2 = 't';

	short int a = 10; //short短整型,int可以省略
	short b = 20; //short是signed short

	signed short c = 30;
	unsigned short d = 40;

	return 0;
}

 Famille virgule flottante :

flotter

double

Type construit (type personnalisé) : 

> Type de tableau

> Type de structure structure

> type d'énumération enum

> union type union

Type de pointeur :

entier *pi ;

caractère *pc ;

flotteur* pf ;

vide* pv ; 

Type vide : 

void signifie type vide (pas de type)

Généralement appliqué au type de retour de la fonction (pas de type de retour), au paramètre de la fonction (pas de paramètre), au type de pointeur (pointeur de type nul)

2. Stockage des formes en mémoire

La création d'une variable consiste à libérer de l'espace en mémoire, et la taille de l'espace est déterminée selon différents types.

Par exemple:

int a = 3;
int b = -1;
int c = 0x11223344;

a, b, c sont en mémoire :

Les données stockées en mémoire sont binaires

Lorsque VS affiche la mémoire, il affiche des données hexadécimales pour la commodité de l'affichage. Un chiffre hexadécimal représente 4 chiffres binaires et deux chiffres hexadécimaux sont 8 chiffres binaires, c'est-à-dire un octet (pour a, 03 00 00 00 signifie quatre octets)

2.1 Code original, code inverse, code complément

Il existe trois méthodes de représentation des nombres entiers dans l'ordinateur, à savoir le code d'origine, le code inverse et le code complémentaire .

Les trois méthodes de représentation ont deux parties : le bit de signe et le bit de valeur. Le bit de signe utilise 0 pour représenter "positif" et 1 pour représenter "négatif", et les trois méthodes de représentation des entiers négatifs pour le bit de valeur sont différentes. 

code d'origine

Il peut être obtenu en traduisant directement le binaire en binaire sous la forme de nombres positifs et négatifs.

son complément

Gardez le bit de signe du code d'origine inchangé, et les autres bits peuvent être obtenus en inversant les autres bits à leur tour. 

complément

Complétez +1 pour obtenir le complément. (Le code d'origine peut également être passé : le bit de signe du code complément reste inchangé, le bit de valeur est inversé bit à bit, puis +1 est obtenu)

 Pour les nombres entiers, il peut être divisé en nombres signés et non signés

Nombre signé : bit de signe + bit de valeur

Nombre positif : 0 + chiffres numériques

Nombres négatifs : 1 + chiffres numériques

//原码 - 有符号数,直接根据正负数值给出的二进制序列就是原码
//反码 - 原码的符号位不变,其他位按位取反
//补码 - 反码二进制的最低位+1得到

//正数的原码、反码、补码相同
int main()
{
	int a = 3;    //signed int a = 3;
	//00000000000000000000000000000011         - 原码
	//00000000000000000000000000000011         - 反码
	//0000 0000 0000 0000 0000 0000 0000 0011  - 补码
	//0    0    0    0    0    0    0    3     - 16进制(内存中)
	
	int b = -1;    //signed int b = -1;
	//10000000000000000000000000000001         - 原码
	//11111111111111111111111111111110         - 反码
	//1111 1111 1111 1111 1111 1111 1111 1111  - 补码
	//f    f    f    f    f    f    f    f     - 16进制(内存中)
   
	return 0;
}

 Pour les bits hexadécimaux 0 1 2 3 4 5 6 7 8 9 abcdef, f peut être obtenu à partir du binaire 1111, et -1 en mémoire est ff ff ff ff

en conclusion:

1. L'original, l'inverse et le complément des nombres positifs sont les mêmes.

2. Pour les nombres entiers : les données stockées dans la mémoire stockent en fait le code complément.

Pourquoi?

Dans les systèmes informatiques, les valeurs sont toujours représentées et stockées dans des nombres complémentaires à 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 de manière uniforme (le CPU n'a qu'un additionneur) Des circuits matériels supplémentaires sont nécessaires.

Par exemple:

int c = 1 - 1;
//CPU只有加法器
//1 - 1 => 1 + (-1),此时用原码来进行计算,结果是错误的(-2)
//用补码来计算则可获得正确结果(符号位也参与计算,进位后丢掉即可)

Pour les nombres non signés : idem pour les entiers positifs.

Pour le caractère (8bit):

pour int c = 0x11223344 ;

Il y a un phénomène de « reverse storage » dans la mémoire, pourquoi ?

2.2 Introduction au Big et Little Endian

Qu'est-ce que le big endian little endian :

Le mode big-endian (stockage) signifie que les bits de poids faible des données sont stockés à l'adresse haute de la mémoire et que les bits de poids fort des données sont stockés à l'adresse basse de la mémoire;

Le mode Little-endian (stockage) signifie que les bits de poids faible des données sont stockés à l'adresse basse de la mémoire et que les bits de poids fort des données sont stockés à l'adresse haute de la mémoire.

pour int c = 0x11223344 ; en mémoire :

Visible : le compilateur actuel utilise le stockage de l'ordre des octets little-endian

Questions d'entretien classiques :

Veuillez décrire brièvement les concepts d'ordre des octets gros-boutiste et d'ordre des octets petit-boutiste, et concevoir un petit programme pour déterminer l'ordre des octets de la machine actuelle. (10 points)

//写一个代码,判断当前机器使用的大端还是小端

//方法1
int check_sys()
{
	int a = 1;
	return (*(char*)&a);
}
int main()
{
	int a = 1;
	int ret = check_sys();
	if (ret == 1)
	{
		printf("小端\n");
	}
	else
	{
		printf("大端\n");
	}
	return 0;
}

//方法2
int main()
{
	int a = 1;
	char* p = (char*)&a;//对p解引用则访问一个字节 
	if (*p == 1)
	{
		printf("小端");
	}
	else
	{
		printf("大端");
	}
	//
	//0x 00 00 00 01
	//
	//低       高
	//小端  
	//01 00 00 00
	//大端
	//00 00 00 01

	//只需要看第一个字节是00还是01即可判断
	return 0;
}

2.3 Questions pratiques

Quelle est la sortie du code suivant ?

//1.
//输出什么?
#include <stdio.h>
int main()
{
	char a = -1;
	//大部分编译器基本上都是signed char
	//11111111 - a
	//11111111111111111111111111111111 - 整形提升后(此时是补码)
	//10000000000000000000000000000001 - 原码  -1
	signed char b = -1;
	//11111111 - b 
	//计算过程和a一样 -1
	unsigned char c = -1;
	//11111111 - c
	//00000000000000000000000011111111 - 正数的原反补码都相同
	//十进制 : 255

	printf("a=%d,b=%d,c=%d", a, b, c);//a = -1, b = -1, c = 255
	//打印%d时会发生整形提升(按照符号位提升)
	return 0;
}
//2.
#include <stdio.h>
int main()
{
	char a = -128;
	//10000000000000000000000010000000 - -128原码
	//11111111111111111111111110000000 - -128补码
	//1000000                          - 存到a中
	//整形提升后:
	//11111111111111111111111110000000 - 4294967168 (2^32 - 127 - 1)
	
    //%u : 打印无符号整形
	printf("a = %u\n", a); //a = 4294967168
	return 0;
}
//3.
#include <stdio.h>
int main()
{
	char a = 128;
	//00000000000000000000000010000000 - 128原码
	//10000000                         - 存到a中
	//整形提升后
	//11111111111111111111111110000000 - 4294967168
	printf("a = %u\n", a); // a = 4294967168
	return 0;
}
//4.
int main()
{
	int i = -20;
	//10000000000000000000000000010100 - -20原码
	//11111111111111111111111111101100 - -20补码
	unsigned  int  j = 10;
	//00000000000000000000000000001010 -  10原(补)码
	
	//11111111111111111111111111110110 - i + j 补码
	//10000000000000000000000000001010 - i + j 原码: -10
	printf("i+j = %d\n", i + j); // i+j = -10
	//按照补码的形式进行运算,最后格式化成为有符号整数

    return 0;
}
//5.
int main()
{
	unsigned int i;//因为i定义的是无符号类型 则始终有:i >= 0; 

	for(i = 9; i >= 0; i--)
	{
		printf("%u\n", i); //死循环
		//当i = -1存入内存中(之后以此类推):
		//11111111111111111111111111111111 - 当做无符号数处理 - 一个巨大的正数
	}
	return 0;
}
//6.
int main()
{
	char a[1000];
	int i;
	for (i = 0; i < 1000; i++)
	{
		a[i] = -1 - i;
	}
	//a[i]从-1 -2 ... -128 共有128个数字
	//对于char来说 负数最多到 : -128
	//当存入-129时,对于char而言是放不下的
	//10000000000000000000000010000001 - -129原码
	//11111111111111111111111101111111 - -129补码
	//01111111                         - -129存入char中 - 127
	//以此类推,存入-130时,内存中实际存储的是126
	//127 126 ... 3 2 1 0,0之前共有127个数字(因为strlen遇到0就终止了)
	//127 + 128 = 255
	printf("%d", strlen(a));//255
	return 0;
}
//7.
#include <stdio.h>
unsigned char i = 0;
//对于无符号的char 取值范围是[0 , 255]
//对于下面代码,i <= 255恒成立,故死循环了
int main()
{
	for (i = 0; i <= 255; i++)
	{
		printf("hello world\n");//死循环
	}
	return 0;
}

 Remarque : Pour les caractères signés, souvenez-vous du schéma suivant (plage de caractères non signés : [ 0 , 255] ) :

3. Stockage en virgule flottante en mémoire

Nombres à virgule flottante courants :

3.14159

1E10 (notation scientifique, 1.0 * 10^10)

La famille des virgules flottantes comprend les types float, double, long double.

La plage représentée par les nombres à virgule flottante : définie dans float.h (famille d'entiers : définition de la plage de valeurs -> limits.h)

3.1 Un exemple de stockage en virgule flottante : 

int main()
{
	int n = 9;
	float* pFloat = (float*)&n; //把int*类型指针强制转换成float*类型
	printf("n的值为:%d\n", n); //9
	printf("*pFloat的值为:%f\n", *pFloat);//0.000000
	*pFloat = 9.0;
	printf("n的值为:%d\n", n); //1091567616
	printf("*pFloat的值为:%f\n", *pFloat); //9.000000
	
	return 0;
}

Description : Le stockage des nombres à virgule flottante est différent de celui des nombres entiers. Alors, comment les nombres à virgule flottante sont-ils stockés ?

3.2 Règles de stockage en virgule flottante

Selon la norme internationale IEEE (Institute of Electrical and Electronics Engineering) 754, tout nombre binaire à virgule flottante V peut être représenté sous la forme suivante :

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

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

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

2^E signifie le bit d'exposant.

Pour 5.5 :

IEEE 754 stipule : Pour un nombre à virgule flottante de 32 bits, le 1 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 significande M :

Par exemple:

int main()
{
	float f = 5.5f;//如果不写f,则默认是double类型
	//101.1
	//1.011 * 2^2
	//(-1)^0 * 1.011 * 2^2
	//S = 0,E =10000001 (2 + 127),M = 011(后面再补20个0)
	//0100 0000 1011 0000 0000 0000 0000 0000 - 内存中存储的二进制
	//4    0    11   0    0    0    0    0    
	//40 B0 00 00 - 十六进制

	return 0;
}

f = 5.5f en mémoire :

 Pour les nombres à virgule flottante 64 bits, le 1 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 également des dispositions spéciales pour la significande M et l'exposant E :

Comme mentionné précédemment, 1≤M<2, c'est-à-dire que M peut être écrit sous la forme 1.xxxxxx, où xxxxxx représente la partie fractionnaire. IEEE 754 stipule que lorsque M est stocké 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, seul 01 est enregistré et lorsqu'il est lu, le premier 1 est ajouté . Le but est d'économiser 1 chiffre significatif. En prenant un nombre à virgule flottante de 32 bits comme exemple, il ne reste que 23 bits pour M. Après avoir arrondi le 1 du premier chiffre, 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 avoir des nombres négatifs, donc IEEE754 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 8 bits E, ce nombre intermédiaire est 127 ; pour 11- bit E, le nombre du milieu est 1023 . Par exemple, le E de 2^10 est 10, donc lorsqu'il est stocké sous forme de nombre à virgule flottante 32 bits, il doit être stocké sous la forme 10+127=137, soit 10001001.

Deuxièmement, l'index E est extrait de la mémoire et peut être divisé en trois cas :

E n'est pas tout à 0 ou pas tout à 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 de 127 (ou 1023) pour obtenir la valeur réelle, puis le premier 1 est ajouté avant le significatif figure M

Par exemple : La forme binaire de 0,5 (1/2) est 0,1. Puisqu'il est stipulé que la partie positive doit être 1, c'est-à-dire que la virgule est décalée vers la droite de 1, alors c'est 1,0*2^( -1), et son code d'ordre est -1+127= 126 est représenté par 01111110, et la mantisse 1.0 supprime la partie entière par 0, et remplit de 0 à 23 bits 00000000000000000000000, alors sa représentation binaire est :

0 01111110 00000000000000000000000

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

Par exemple:

    //0 00000000 01000100101000000000000
	//E+127存入数据后是00000000
	//真实的E = -127
	//(-1)^0 * 1.01000100101 * 2^(-127) - 无限接近于0的数字
	
	//所以对于接近0的数字:
	//M拿出来不+1,E = -126(32位)
	//(-1)^0 * 0.01000100101 * 2^(-126) - 真实取出时的数字,也无限接近于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)

	//E为全1
	//E + 128 = 255
	//E = 127
	//(+ -) * 1.xxxxxx * 2 ^ 128 - 趋近于正负无限大

Concernant les règles de représentation des nombres à virgule flottante, nous en parlerons ici.

Avec la base ci-dessus : Revenons à la question du début : pourquoi 0x00000009 est-il restauré en nombre à virgule flottante, il devient 0,000000 ?

int main()
{
	int n = 9;
	//00000000000000000000000000001001 - 二进制

	float* pFloat = (float*)&n; //把int*类型指针强制转换成float*类型
	printf("n的值为:%d\n", n); //9
	
	printf("*pFloat的值为:%f\n", *pFloat);//0.000000
	//*pFloat - 以浮点数的视角去访问n的四个字节,就会认为n的4个字节中放的是浮点数
	//0 00000000 00000000000000000001001 (E全0的情况)
	//(-1)^0 * 2 ^ (-126) * 0.00000000000000000001001
	//0.000000
	
	*pFloat = 9.0;
	//*pFloat - 以浮点数的视角观察n的4个字节
	//以浮点数的形式存储9.0
	//1001.0 - 二进制
	//1.001 * 2^3 - 科学计数法
	//(-1)^0 * 1.001 * 2^3
	//S = 0,E = 130(3 + 127). M = 00100000000000000000000 
	//0 10000010 00100000000000000000000 - 内存中存储形式
	//1091567616 - 二进制
	printf("n的值为:%d\n", n); //1091567616
	printf("*pFloat的值为:%f\n", *pFloat); //9.000000

	return 0;
}

Je suppose que tu aimes

Origine blog.csdn.net/m0_62934529/article/details/123702995
conseillé
Classement