Almacenamiento de datos en la computadora - [lenguaje C]

En el blog anterior, hemos aprendido los tipos de datos del lenguaje C. Revisemos primero los tipos de datos en el lenguaje C.


Tabla de contenido

Tipos básicos integrados del lenguaje C

Clasificación básica de tipos

Almacenamiento de enteros en la memoria

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

endianidad en almacenamiento

práctica 

 Almacenamiento de tipos de punto flotante en la memoria

 Reglas de almacenamiento para números de punto flotante

Análisis en profundidad de los problemas de citas 


Tipos básicos integrados del lenguaje C

char //Tipo de datos de carácter

short //tipo de dato entero corto

int //entero    

largo // entero largo

largo largo//largo entero largo

float // tipo de punto flotante de precisión simple

double // tipo de punto flotante de precisión doble

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

 También podemos tener una comprensión general de los valores máximos y mínimos de los tipos integrados:

Ya estamos familiarizados con estos tipos y tamaños de datos integrados básicos. A continuación, clasificaremos aproximadamente los tipos de datos en lenguaje C.


Clasificación básica de tipos

 familia entera:

carbonizarse

        carácter firmado

        carácter sin firmar

corto

        firmado corto [int]

        corto sin firmar [int]

En t

        firmado ent

        int sin firmar

largo

        firmado largo [int]

        largo sin firmar [int]

Hablemos de char en detalle. Char se divide en char firmado y char sin firmar, que son caracteres firmados y caracteres sin firmar respectivamente. El tamaño de char es de 8 bits, por lo que hay 8 bits binarios. Cuando es char firmado, el bit más alto es un bit de signo, cuando es un carácter sin firmar, son todos bits digitales. Por lo tanto, el carácter con signo y el carácter sin signo representan rangos diferentes, y los resultados son los siguientes:

 Cuando el número es mayor que el rango que se puede representar, el número volverá al principio y continuará el ciclo.

Int, corto, largo puede referirse al contenido anterior.

Familia de punto flotante:

 flotar

doble

Familia de punteros:

int *p;

carácter *p;

flotante *p;

vacío *p;

......

 Tipo de construcción:

Tipo de matriz: int arr[10]

Tipo de estructura: estructura

Tipo de unión: unión

Tipo de enumeración: enum

Tipo vacío: void significa tipo vacío (sin tipo)

Por lo general, el valor de retorno de la función, el parámetro de la función, el tipo de puntero.

Lo anterior es nuestra clasificación y disposición de los tipos de datos en todo el lenguaje C. A continuación, ¡aprendamos un poco de cosas subyacentes para profundizar nuestro conocimiento y comprensión de los datos!


Almacenamiento de enteros en la memoria

Escribí en el blog anterior que una variable necesita abrir un espacio de memoria cuando se crea, y el tamaño del espacio está relacionado con el tipo de datos.

¿Cómo se almacena esa variable entera en la computadora?

Cuando creamos un int a = 10, la computadora abrirá un espacio de 4 bytes para almacenar los datos en él y lo almacenará en orden binario (la visualización en vs está en hexadecimal), es decir, 32 Una combinación de 0 o 1 está almacenado. 

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

Hay tres métodos de representación binaria para números enteros en computadoras, a saber, código original, código complementario y código complementario. Los tres métodos de representación tienen dos partes, un bit de signo y un bit de valor. El bit de signo usa 0 para representar "positivo" y 1 para representar "negativo". Hay tres formas diferentes de representar números enteros negativos.

Código original: El código original se puede obtener traduciendo directamente el valor a binario en forma de números positivos y negativos.

Código inverso: mantenga el bit de signo del código original sin cambios e invierta el resto del código poco a poco.

Complemento: Suma +1 al complemento resultante. 

Para datos enteros, la computadora generalmente los almacena en forma de complemento a dos: porque en el sistema informático, todos los valores se expresan y almacenan en complemento a dos. La razón es que, utilizando el código de complemento, el bit de signo y el campo de valor 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). del código complementario y el código original es el mismo, no se requiere un circuito de hardware adicional. 

Echemos un vistazo al código original, el código inverso y el código complementario de 10 y -10

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

En comparación con el almacenamiento en la computadora, podemos encontrar que el orden de almacenamiento es un poco incorrecto, entonces, ¿por qué es esto? 

 


endianidad en almacenamiento

¿Qué es endian grande y pequeño?

El modo big-endian (almacenamiento) significa que los bits bajos de los datos se almacenan en las direcciones altas de la memoria, y los bits altos de los datos se almacenan en las direcciones bajas de la memoria.

El modo little endian (almacenamiento) significa que los bits bajos de los datos se almacenan en las direcciones bajas de la memoria, mientras que los bits altos de los datos se almacenan en las direcciones altas de la memoria. 

¿Por qué necesitamos distinguir entre el almacenamiento big endian y el small endian?

Esto se debe a que en el sistema informático usamos bytes como unidad, y cada unidad de dirección corresponde a un byte, y un byte tiene 8 bits. Pero en el lenguaje C, además del char de 8 bits, también existen tipos cortos de 16 bits y tipos largos de 32 bits (dependiendo del compilador específico).Además, para procesadores con más de 8 bits, como 16 bits O para un procesador de 32 bits, dado que el ancho del registro es mayor que un byte, debe haber un problema de cómo organizar varios bytes. Por lo tanto, conduce al modo de almacenamiento big-endian y al modo de almacenamiento little-endian. Por ejemplo: un tipo corto de 16 bits x, la dirección en la memoria es 0x0010, el valor de x es 0x1122, luego 0x11 es el byte alto y 0x22 es el byte bajo. Para el modo big-endian, coloque 0x11 en la dirección baja, es decir, 0x0010, y coloque 0x22 en la dirección alta, es decir, 0x0011. Modo little endian, todo lo contrario. Nuestra estructura X86 de uso común es el modo little-endian, mientras que KEIL C51 es el modo big-endian. Muchos ARM y DSP están en modo little-endian. Algunos procesadores ARM también pueden elegir el modo big-endian o el modo little-endian por hardware.

¿Cómo distinguimos entre usar programas para distinguir si nuestra computadora es big-endian o little-endian?

Podemos usar la diferencia entre big endian y small endian para comenzar el problema. El método de almacenamiento de big endian y little endian es justo opuesto. Podemos establecer un valor fijo de 1 y luego usar el puntero para acceder a la ubicación de la dirección baja para imprimir Si el impreso Si el número es 1, es almacenamiento little-endian, de lo contrario, es almacenamiento big-endian. (La variable creada es int, en este momento solo necesitamos acceder al contenido de un byte y usar la conversión de tipo obligatoria para cambiar la dirección de la variable a (char *)). ¡Se forma la teoría, comienza la práctica!

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

práctica 

 Lo anterior es el contenido básico del almacenamiento de enteros, podemos consolidarlo a través de algunos ejercicios:

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

Analicemos esta pregunta primero: el rango que puede representar el carácter sin signo es (0 ~ 255).En el ciclo for, si el valor de i excede 255, puede saltar fuera del ciclo, pero el valor máximo que puedo representar es 255. Si agrega 1 más El valor de i volverá a 0 para continuar el ciclo, por lo que este programa es un ciclo sin 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-补码

Esta pregunta es para distinguir el rango de números representados por char, char con signo y char sin signo. En general, char significa char con signo, por lo que los valores impresos de a y b deben ser iguales, ambos son -1. Para caracteres sin signo, primero truncamos el complemento de -1 a 11111111, porque el formato impreso es %d, por lo que el entero sin signo de 11111111 se promociona a 00000000000000000000000011111111, y el resultado impreso debe ser 255, por lo que el resultado impreso final debe ser: 1, -1, 255.

Hemos terminado de leer el almacenamiento de enteros, entonces, ¿cómo debemos almacenar los tipos de punto flotante?


 Almacenamiento de tipos de punto flotante en la memoria

Los números de punto flotante también son visitantes frecuentes en nuestro lenguaje C, y hay muchos números de punto flotante a nuestro alrededor: 3.14, 13.14... Como se mencionó anteriormente, la familia de números de punto flotante incluye float y double.

El rango de números de punto flotante se puede consultar en #include<float.h>.

Usamos un ejemplo para presentar el almacenamiento de tipos de punto flotante en la memoria:

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

 El resultado de salida es:

El uso de un puntero de punto flotante para desreferenciar un entero lee el valor de forma diferente a la esperada y, a la inversa, el uso de un número de punto flotante para recuperarlo de la memoria no devuelve el valor esperado. De esto podemos concluir que las reglas de almacenamiento y lectura de números enteros y de punto flotante son diferentes. ¿Cómo deben almacenarse en la memoria los números de coma flotante?


 Reglas de almacenamiento para números 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 expresar de la siguiente forma:

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

(-1)^S representa el bit de signo, cuando S=0, V es un número positivo, cuando S=1, V es un número negativo.

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

2^E significa bits de exponente.

Por ejemplo:

5,0 en decimal es 101,0 en binario, lo que equivale a 1,01 × 2 ^ 2. Entonces, según el formato de V anterior, se puede concluir que S=0, M=1.01 y E=2. El decimal -5.0, escrito en binario es -101.0, que equivale a -1.01×2^2. Entonces, S=1, M=1.01, E=2.

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 número efectivo M. 

Para un número de coma flotante de 64 bits, el bit más alto es el bit de signo S, los 11 bits siguientes son el exponente E y los 52 bits restantes son la mantisa M.  IEEE 754 tiene algunas regulaciones especiales sobre el número efectivo M y el exponente E:

 Como se mencionó anteriormente, 1≤M<2, es decir, M se puede escribir en forma de 1.xxxxxx, donde xxxxxx representa la parte decimal. IEEE 754 estipula que cuando se guarda M dentro de 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 guarde 01 y luego agregue el primer 1 al leer. El propósito de hacer esto es ahorrar 1 cifra significativa. Tome el número de punto flotante de 32 bits como ejemplo, solo quedan 23 bits para M, y después de descartar el primer 1, es igual a guardar 24 cifras significativas.

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

 Primero, E es un entero sin signo (int sin signo) , lo que 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 IEEE 754 estipula que se debe agregar un número intermedio al valor real de E cuando se almacena en la memoria. Para E de 8 dígitos, el número intermedio es 127. Para un E de 11 bits, este número intermedio es 1023. Por ejemplo, la E de 2^10 es 10, por lo que al guardarlo como un número de coma flotante de 32 bits, debe guardarse como 10+127=137, que es 10001001. 

Luego hay tres situaciones en las que se quita E de la memoria:

E no es todo 0 o 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 127 (o 1023) para obtener el valor real, y luego se suma el primer dígito 1 antes del numero efectivo m

Por ejemplo: la forma binaria de 0,25 (1/4) es 0,01, como la parte positiva debe ser 1, es decir, el punto decimal se mueve 2 bits a la derecha, es 1,0*2^(-2), y su código de pedido es -2+127= 125, expresado como 01111101, y la mantisa 1.0 elimina la parte entera para que sea 0, y llena de 0 a 23 dígitos 00000000000000000000000, entonces su representación binaria es: 00111110100000000000000000000 00

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 número significativo M ya no suma el primer 1, sino que se restaura a un decimal de 0,xxxxxx. Esto se hace para representar ±0 y números muy pequeños cercanos a 0.

E es todo 1: 

En este momento, si el número significativo M es todo 0, significa ± infinito (positivo o negativo depende del bit de signo s)


Análisis en profundidad de los problemas de citas 

Ahora volvamos atrás y analicemos el ejemplo anterior:

El valor binario de 9 es 000000000000000000000000000001001, ¿por qué se convierte a un número de coma flotante con nueve colas 0.000000?  Dado que el exponente E es todo 0, se cumple el segundo caso de la sección anterior. Por lo tanto, el número de coma flotante V se escribe como: V=(-1)^0 × 0.00000000000000000001001×2^(-126)=1.001×2^(-146) Obviamente, V es un pequeño número positivo cercano a 0, entonces En notación decimal es 0.000000.

Analicemos los problemas que surgen al convertir números de coma flotante a enteros: 

 El entero decimal resultante es el resultado de nuestro programa.

Lo anterior es todo el contenido de los datos en la computadora. Espero que pueda obtener algo a través de mi artículo. ¡Su apoyo es mi mayor motivación! ! !

Supongo que te gusta

Origin blog.csdn.net/m0_74755811/article/details/131613077
Recomendado
Clasificación