Lenguaje C avanzado: análisis en profundidad del almacenamiento de datos en la memoria

contenido

1. Introducción a los tipos de datos

1.1 Clasificación básica de tipos

2. Forma de almacenamiento en la memoria

2.1 Código original, código inverso, código complemento

2.2 Introducción a Big y Little Endian

2.3 Ejercicios

3. Almacenamiento de coma flotante en la memoria

3.1 Un ejemplo de almacenamiento de punto flotante: 

3.2 Reglas de almacenamiento de punto flotante


1. Introducción a los tipos de datos

Hemos aprendido acerca de los tipos integrados básicos y la cantidad de almacenamiento que ocupan

char //Tipo de dato de carácter, ocupando 1 byte

short //Entero corto, ocupando 2 bytes

int //forma, ocupa 4 bytes

long //Entero largo, ocupando 4 bytes

long long //Entero más largo, ocupando 8 bytes

float //Número de punto flotante de precisión simple, que ocupa 4 bytes

double //Número de punto flotante de precisión doble, que ocupa 8 bytes

Escriba significado:

1. Use este tipo para ampliar el tamaño del espacio de memoria (el tamaño determina el rango de uso).

2. Cómo mirar la perspectiva del espacio de la memoria. (Por ejemplo, int y float son ambos de 4 bytes, uno es un número entero y el otro es un decimal al mirarlo)

1.1 Clasificación básica de tipos

familia entera:

carbonizarse

        carácter sin firmar

        carácter firmado

pequeño

        corto sin signo [int], [int] se puede omitir

        firmado corto [int], [int] se puede omitir

En t

        int sin firmar

        firmado ent

largo

        sin signo largo [int], [int] se puede omitir

        firmado largo [int], [int] se puede omitir

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;
}

 Familia de punto flotante:

flotador

doble

Tipo construido (tipo personalizado): 

> Tipo de matriz

> Tipo de estructura struct

> tipo de enumeración enum

> unión tipo unión

Tipo de puntero:

int*pi;

char *pc;

flotante* pf;

vacío* pv; 

Tipo vacío: 

void significa tipo vacío (sin tipo)

Por lo general, se aplica al tipo de retorno de la función (sin tipo de retorno), el parámetro de la función (sin parámetro), el tipo de puntero (puntero de tipo nulo)

2. Forma de almacenamiento en la memoria

La creación de una variable es para abrir espacio en la memoria, y el tamaño del espacio se determina según diferentes tipos.

P.ej:

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

a, b, c están en la memoria:

Los datos almacenados en la memoria son binarios.

Cuando VS muestra la memoria, muestra datos hexadecimales para facilitar la visualización. Un dígito hexadecimal representa 4 dígitos binarios y dos dígitos hexadecimales son 8 dígitos binarios, es decir, un byte (para a, 03 00 00 00 significa cuatro bytes)

2.1 Código original, código inverso, código complemento

Hay tres métodos de representación de números enteros en la computadora, a saber , código original, código inverso y código complemento .

Los tres métodos de representación tienen dos partes: el bit de signo y el bit de valor. El bit de signo usa 0 para representar "positivo" y 1 para representar "negativo", y los tres métodos de representación de enteros negativos para el bit de valor son diferentes. 

codigo original

Se puede obtener traduciendo directamente binario a binario en forma de números positivos y negativos.

el complemento de uno

Mantenga el bit de signo del código original sin cambios, y los otros bits se pueden obtener invirtiendo los otros bits a su vez. 

complemento

Complemento +1 para obtener complemento. (El código original también se puede pasar: el bit de signo del código complementario permanece sin cambios, el bit de valor se invierte poco a poco y luego se obtiene +1)

 Para números enteros, se puede dividir en números con signo y sin signo

Número con signo: bit de signo + bit de valor

Número positivo: 0 + dígitos numéricos

Números negativos: 1 + dígitos numéricos

//原码 - 有符号数,直接根据正负数值给出的二进制序列就是原码
//反码 - 原码的符号位不变,其他位按位取反
//补码 - 反码二进制的最低位+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;
}

 Para los bits hexadecimales 0 1 2 3 4 5 6 7 8 9 abcdef, f se puede obtener del binario 1111, y -1 en la memoria es ff ff ff ff

En conclusión:

1. El original, el inverso y el complemento de los números positivos son iguales.

2. Para números enteros: los datos almacenados en la memoria en realidad almacenan el código de complemento.

¿Por qué?

En los sistemas informáticos, los valores siempre se representan y almacenan en números complemento a dos. La razón es que al usar el código complementario, el bit de signo y el campo numérico se pueden procesar de manera uniforme; al mismo tiempo, la suma y la resta también se pueden procesar de manera uniforme (la CPU solo tiene un sumador) Se requiere un circuito de hardware adicional.

P.ej:

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

Para números sin signo: igual que los enteros positivos.

Para caracteres (8 bits):

para int c = 0x11223344;

Hay un fenómeno de "almacenamiento inverso" en la memoria, ¿por qué?

2.2 Introducción a Big y Little Endian

Que es big endian little endian:

El modo big-endian (almacenamiento) significa que los bits de orden inferior de los datos se almacenan en la dirección superior de la memoria, y los bits de orden superior de los datos se almacenan en la dirección inferior de la memoria;

El modo Little-endian (almacenamiento) significa que los bits de orden inferior de los datos se almacenan en la dirección inferior de la memoria, y los bits de orden superior de los datos se almacenan en la dirección superior de la memoria.

para int c = 0x11223344; en memoria:

Visible: el compilador actual usa almacenamiento de orden de bytes little-endian

Preguntas clásicas de la entrevista:

Describa brevemente los conceptos de orden de bytes big-endian y orden de bytes little-endian, y diseñe un pequeño programa para determinar el orden de bytes de la máquina actual. (10 puntos)

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

//方法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 Preguntas de práctica

¿Cuál es la salida del siguiente código?

//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;
}

 Nota: Para caracteres firmados, recuerde el siguiente diagrama (rango de caracteres sin firmar: [ 0 , 255] ):

3. Almacenamiento de coma flotante en la memoria

Números comunes de coma flotante:

3.14159

1E10 (notación científica, 1.0 * 10^10)

La familia de punto flotante incluye: tipos float, double, long double.

El rango representado por números de punto flotante: definido en float.h (familia de enteros: definición del rango de valores -> límites.h)

3.1 Un ejemplo de almacenamiento de punto flotante: 

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;
}

Descripción: El almacenamiento de números de coma flotante es diferente al de los enteros. Entonces, ¿cómo se almacenan los números de punto flotante?

3.2 Reglas de almacenamiento de punto flotante

De acuerdo con el estándar internacional IEEE (Instituto de Ingeniería Eléctrica y Electrónica) 754, cualquier número binario de coma flotante V se puede representar de la siguiente forma:

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

(-1)^s representa el bit de signo. Cuando s=0, V es positivo, cuando s=1, V es negativo.

M representa un número significativo, mayor o igual a 1 y menor a 2 .

2^E significa el bit del exponente.

Para 5.5:

IEEE 754 estipula: Para un número de punto flotante de 32 bits, el bit más alto es el bit de signo s, los siguientes 8 bits son el exponente E y los 23 bits restantes son el significado M:

P.ej:

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 memoria:

 Para números de coma flotante de 64 bits, el bit más alto es el bit de signo S, los siguientes 11 bits son el exponente E y los 52 bits restantes son la mantisa M

 

IEEE 754 también tiene algunas disposiciones especiales para el significado M y el exponente E:

Como se mencionó anteriormente, 1≤M<2, es decir, M puede escribirse en forma de 1.xxxxxx, donde xxxxxx representa la parte fraccionaria. IEEE 754 estipula que cuando M se almacena en la computadora, el primer dígito de este número siempre es 1 por defecto , por lo que se puede descartar y solo se guarda la siguiente parte xxxxxx. Por ejemplo, al guardar 1.01, solo se guarda 01 , y cuando se lee, se agrega el primer 1 . El propósito de esto es ahorrar 1 cifra significativa. Tomando como ejemplo un número de punto flotante de 32 bits, solo quedan 23 bits para M. Después de redondear el 1 en el primer dígito, se pueden guardar 24 dígitos significativos.

En cuanto al índice E, la situación es más complicada:

Primero, E es un entero sin signo (int sin signo),

Esto significa que si E tiene 8 bits, su rango de valores es 0~255; si E tiene 11 bits, su rango de valores es 0~2047. Sin embargo, sabemos que E en notación científica puede tener números negativos, por lo que IEEE754 estipula que el valor real de E debe agregarse con un número intermedio cuando se almacena en la memoria . Para E de 8 bits, este número intermedio es 127; para 11- bit E, el número del medio es 1023 . Por ejemplo, la E de 2^10 es 10, por lo que cuando se almacena como un número de coma flotante de 32 bits, debe almacenarse como 10+127=137, que es 10001001.

En segundo lugar, el índice E se obtiene de la memoria y se puede dividir en tres casos:

E no es todo 0 o no todo 1

En este momento, el número de punto flotante se representa mediante las siguientes reglas, es decir, el valor calculado del exponente E se resta de 127 (o 1023) para obtener el valor real, y luego se suma el primer 1 antes del significativo figura m

Por ejemplo: la forma binaria de 0,5 (1/2) es 0,1. Dado que se estipula que la parte positiva debe ser 1, es decir, el punto decimal se desplaza a la derecha en 1, entonces es 1,0*2^( -1), y su código de orden es -1+127= 126 se representa como 01111110, y la mantisa 1.0 elimina la parte entera como 0, y completa de 0 a 23 bits 000000000000000000000000, entonces su representación binaria es:

0 01111110 000000000000000000000000

E es todo 0:

En este momento, el exponente E del número de coma flotante es igual a 1-127 (o 1-1023), que es el valor real.

El dígito significativo M ya no se suma con el primer 1 , sino que se reduce a un decimal de 0.xxxxxx . Esto se hace para representar ±0 y números muy pequeños cercanos a 0 . ​​​​​​​

P.ej:

    //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 es todo 1

En este momento, si los dígitos significativos M son todos 0, significa ± infinito (positivo o negativo depende del bit de signo s)

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

Acerca de las reglas de representación de los números de punto flotante, hablaremos de ello aquí.

Con la base anterior: volvamos a la pregunta del principio: ¿por qué 0x00000009 se restaura a un número de punto flotante, se convierte en 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;
}

Supongo que te gusta

Origin blog.csdn.net/m0_62934529/article/details/123702995
Recomendado
Clasificación