[C Avanzado] Análisis en profundidad del almacenamiento de datos en la memoria

Tabla de contenido

1. Introducción de tipos de datos

1. El significado del tipo:

2. Clasificación básica de tipos.

2. Almacenamiento de modelado en la memoria.

1. Complemento de código inverso del código original

2. Introducción al endian grande y pequeño

3. Practica

3. Almacenamiento de tipos de punto flotante en la memoria.

1. Un ejemplo

  2. Reglas de almacenamiento de números de punto flotante


1. Introducción de tipos de datos

Anteriormente hemos conocido los tipos integrados básicos y el tamaño del espacio de almacenamiento que ocupan:

char //Tipo de datos de carácter

corto //entero corto

int // dando forma

largo //forma larga

largo largo //forma más larga

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

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

1. El significado del tipo:

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

2. Determine cómo mirar la perspectiva del espacio de la memoria:

Los tipos int y float son ambos de 4 bytes, pero uno es un número entero y el otro es un tipo de punto flotante, y la perspectiva de ver el espacio de memoria es diferente.

2. Clasificación básica de tipos.

(1) Familia de cirugía plástica:

carbonizarse:

        carácter sin firmar

        carácter firmado

corto :

       corto sin signo [int] // entero corto, este entero int se puede omitir

        firmado corto [int]

En t :

      entero sin firmar

        firmado entero

largo :

       largo sin firmar [int]

        firmado largo [int]

[Recordatorio]: El tipo de carácter también es el motivo de la familia de configuración:

El carácter se almacena en la memoria como el valor del código ASCII (0-127) del carácter , y el valor del código ASCII es un número entero, por lo que el tipo de carácter se clasifica en la familia de números enteros) 

firmado - firmado: cuando el primer bit representa el bit de signo, está firmado

sin signo - sin signo: sin signo cuando cada bit es un bit de valor y un bit significativo

【Aviso】:

Cuando no escribimos firmado y sin firmar, los tipos int, short y long están firmados de forma predeterminada

por ejemplo: cuando escribimos int a, el valor predeterminado en realidad es el tipo int firmado

pero  : el lenguaje C no estipula si char está firmado (esto depende del compilador, la mayoría de ellos están firmados)


(2) Familia de coma flotante: todos pueden representar decimales

 float //precisión más pequeña, precisión única

double //Mayor precisión, doble precisión


(3) Tipo de construcción (tipo personalizado)

> tipo de matriz

> tipo de estructura estructura

> enumeración tipo enumeración

> unión tipo unión


(4) tipo de puntero

entero *pi

carácter *pc

flotar *pf

void * pv (puntero sin tipo concreto)


(5) tipo vacío

void significa tipo vacío (sin tipo)

Generalmente se aplica al tipo de retorno de la función, el parámetro de la función, el tipo de puntero

por ejemplo: int main (void) significa que la función principal no requiere parámetros

Pero, de hecho, la función principal es un int main(int argc, char *argv[ ], char *envp[ ]) con tres parámetros. Estos tres parámetros deben escribirse solo cuando sean necesarios. Puede escribir void directamente sin paréntesis

2. Almacenamiento de modelado en la memoria.

Las computadoras pueden procesar datos binarios y los números enteros y de punto flotante también se almacenan en forma binaria en la memoria.

1. Complemento de código inverso del código original

Hay tres tipos de representaciones binarias de números enteros: código original, código inverso y código complementario.

Enteros positivos: el código original, el código inverso y el código complementario son iguales

Enteros negativos: es necesario calcular el código original, el código inverso y el código complementario

Los números enteros se almacenan en la memoria como secuencias binarias en complemento a dos.

p.ej:

int a = -10;// el tipo int ocupa 4 bytes -32 bits
    10000000 00000000 00000000 00001010 código original
    11111111 11111111 11111111 11110101 código inverso
    1 1111111 11111111 1 111111 1 11110110 código complementario (el bit más alto representa el bit de signo y los otros 31 bits representan los bits de valor)

    unsigned int b = -10;
    1 1111111 11111111 11111111 11110110 Código complementario (los 32 bits representan bits de valor)


Para dar forma, los datos almacenados en la memoria son en realidad el código complementario.

¿por qué?

Usando 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 ). Además, el código de complemento y el código original se convierten entre sí, y el proceso de operación es el mismo sin circuito de hardware adicional

p.ej:

    La computadora 1-1
    se convierte en 1+(-1)
    00000000 00000000 00000000 00000001 El complemento inverso original de 1
    10000000 00000000 00000000 00000001 El código original de -1
    11111111 11111111 11 111111 11111110 Complemento a 1 11111111 11111111
    11111111 11111111 Complemento a 1
    si es simplemente sumando el código original para obtener -2 (dudaré si agregar el bit de signo)
    pero si es sumando el código complemento, se obtiene el resultado correcto, y cada bit se incrementa continuamente en 1, y finalmente se agrega un bit adicional al frente, el bit es 1 y se descarta directamente, y los otros bits son 0

2. Introducción al endian grande y pequeño

int a=0x11223344 (según el almacenamiento de datos, 44 se encuentra en el byte bajo, 11 se encuentra en el byte alto)

Almacenamiento big endian:

Almacene los datos en el byte bajo de un dato en la dirección alta de la memoria y almacene los datos en el byte alto en la dirección baja de la memoria

Almacenamiento little-endian:

Almacene los datos en el byte bajo de un dato en la dirección baja de la memoria y almacene los datos en el byte alto en la dirección alta de la memoria

[Nota]: cuando se almacenan datos, el orden de discusión se almacena en bytes, por lo que se denomina almacenamiento endian grande y pequeño.

El tipo char no necesita considerar el tamaño del final , el tipo char ocupa un byte y no hay ningún orden


¿Por qué hay un almacenamiento endian grande y pequeño?

Esto se debe a que en el sistema informático utilizamos 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 carácter de 8 bits, también hay 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 Procesador de 16 bits o 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.


Preguntas de la prueba escrita de Baidu:

Describa brevemente los conceptos de big-endian y little-endian y diseñe un programa para determinar el orden de bytes de la máquina actual.

Ideas:

Proporcione una variable a de tipo int : déjela ser 1 (de modo que el sistema hexadecimal sea simplemente 0x 00 00 00 01 ), y luego acceda a un byte a la vez a través de char* e imprímalo para ver si es 00 o 01, para juzgar el tamaño del final.

Código:

#include<stdio.h>
int main()
{
	int a = 1;
	char* p = (char*)&a;   //要将&a(int *)强制转化为char *
	if (*p == 1)
		printf("小端\n");
	else
		printf("大端\n");
	return 0;
}

[Función personalizada para juzgar]:

#include<stdio.h>
int check_sys()
{
	int a = 1;
	return *(char*)&a;
}
int main()
{
	if(check_sys()==1)
		printf("小端\n");
	else
		printf("大端\n");
	return 0;
}

3. Practica

<1> ¿Cuál es el resultado del siguiente programa?

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

Respuesta:

-1 -1 255

explicar:

El primer -1 es un número entero, el código original: 10000000 00000000 00000000 00000001

                         Código inverso: 111111111 111111111 111111111 111111110

                         Complemento: 111111111 111111111 111111111 111111111

Pero el tipo char tiene solo 8 bits, por lo que el complemento es 111111111 y el primer bit es el bit de signo (para a y b)

%d es un entero con signo impreso en forma decimal

Luego es necesario realizar una mejora plástica (el bit superior del número sin signo se rellena con 0 y el bit superior del número con signo se rellena con un bit de signo) (para mejorar la configuración del código original)

Para a y b: el código de complemento después de la promoción plástica es 111111111 111111111 111111111 111111111 (es decir, -1)

Para c: el código de complemento es 00000000 00000000 00000000 111111111 después de dar forma y actualizar (y debido a que es una forma sin firmar, el código de complemento es el mismo que el código original) (es decir, 255)


 <2> ¿Cuál es el resultado del siguiente programa?

#include <stdio.h>
int main()
{
	char a = -128;
	printf("%u\n", a);
	return 0;
}

Respuesta:

4294967168

explicar:

El código original de -128: 10000000 00000000 00000000 10000000

           Código inverso: 111111111 111111111 111111111 011111111

           Complemento: 111111111 111111111 111111111 10000000

Almacenar en el complemento de a: 10000000 (1 es el bit de signo)

Remodelar y actualizar a: 111111111 111111111 111111111 10000000 (bit con signo alto bit de signo complementado 1)

%u es un entero sin signo impreso en forma decimal

Luego imprima como si a fuera un número sin signo y el complemento inverso original del número sin signo fuera el mismo, simplemente calcúlelo directamente


 <3> ¿Cuál es el resultado del siguiente programa?

#include <stdio.h>
int main()
{
	char a = 128;
	printf("%u\n", a);
	return 0;
}

Respuesta:

4294967168

explicar:

Aunque el carácter firmado solo puede tener hasta 127, aún se le puede asignar un valor de 128, que puede truncarse por sí mismo.

El código original de 128: 00000000 00000000 00000000 10000000

Almacenar en el complemento de a: 10000000 (1 es el bit de signo)

Remodelar y actualizar a: 111111111 111111111 111111111 10000000 (bit con signo alto bit de signo complementado 1)

Imprimir en forma decimal sin signo


【Resumir】: 

carácter firmado: -128 ~ 127

char: se supone que es un carácter con signo (1 byte = 8 bits) (el primer bit es el bit de signo) la primera columna es el código original

00000000 0

00000001 1

00000010 2

00000011 3

... ...

011111111  127

10000000 -128   11111111 (invertido) 110000000 (suplemento: se debe eliminar el dígito adicional)

10000001 -127 11111110 111111111

...

111111110 -2 10000001 10000010

111111111 -1 10000000 10000001

259545bbc6cc455e84e6d31bce030392.png  

Supongamos que es un carácter sin firmar: 0~255

 00000000

00000001 1

00000010 2

00000011 3

...                

011111111 127

10000000 128

...

111111110 254

111111111 255

f55ff4f0670841ef97bf266f8929b3c7.png


  <4> ¿Cuál es el resultado del siguiente programa?

int i= -20;
unsigned int j = 10;
printf("%d\n", i+j);

Respuesta:

-10

explicar:

-20: Código original: 10000000 00000000 00000000 00010100

         Código inverso: 111111111 111111111 111111111 11101011

         Complemento: 111111111 111111111 111111111 11101100

10: Código de complemento inverso original: 00000000 00000000 00000000 00001010 (el bit más alto se convierte en el bit de signo al sumar)

Código de complemento para adición: 111111111 111111111 111111111 11110110 (código de complemento)

Código inverso: 10000000 00000000 00000000 00001001

Código original: 10000000 00000000 00000000 00001010 (-10)


   <5> ¿Cuál es el resultado del siguiente programa?

unsigned int i;
for(i = 9; i >= 0; i--)
{
printf("%u\n",i);
}

Respuesta:

9 a 0 y luego a 4294967295, sigue disminuyendo, bucle infinito

explicar:

El rango de int sin signo es> = 0, por lo que la condición de juicio del bucle for siempre es verdadera, de manera análoga a char sin signo, cuando 0 continúa disminuyendo, llega a 255, y lo mismo ocurre con int sin signo


    <6> ¿Cuál es el resultado del siguiente programa?

int main()
{
char a[1000];
int i;
for(i=0; i<1000; i++)
{
a[i] = -1-i;
}
printf("%d",strlen(a));
return 0;
}

Respuesta:

255

explicar:

strlen cuenta el número de caracteres antes de \0 (es decir, 0)

a[ i ] contiene -1, -2, -3...-128 127 ...6 5 4 3 2 1 0

Un total de 128+127=255 números


     <7> ¿Cuál es el resultado del siguiente programa?

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

Respuesta:

Bucle infinito

explicar:

El rango de caracteres sin signo es 0-255, la condición del bucle for siempre es verdadera y entra en un bucle infinito.


3. Almacenamiento de tipos de punto flotante en la memoria.

Números de coma flotante comunes:

3.14159

1E10 (es decir, 1,0*10^10)

La familia de coma flotante incluye: tipos flotante, doble y doble largo.

El rango de números de punto flotante: definido en float.h

1. Un ejemplo

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);
return 0;
}

resultado:

ce965ae2386e42bd865ff15d005098d2.png


  2. Reglas de almacenamiento de números de punto flotante

Cualquier número binario de coma flotante V se puede expresar de la siguiente forma :

8e461bb8b5c646cfb73859976bcdd99.png

por ejemplo: convertir 5,5 en decimal a binario

101,1 (un dígito después del punto decimal es 2 elevado a -1, que es 0,5)

La representación binaria del número de punto flotante es (-1) ^ 0 * 1.011 * 2 ^ 2 (el punto decimal avanza dos dígitos, es decir, * 2 ^ 2 (binario), si es decimal, es 2 ^ 10) 

Obtener: S=0, M=1,011, E=2


Para números 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.

91b86202a4ac410298f2f938092a3cb4.png
Para números de punto 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 el número efectivo M.
 da94adf1bdbd42eb97effc98d4bd4e81.png


Almacenamiento del dígito significativo M:

Para el número efectivo M, 1<=M<2, al guardar M en la computadora, el dígito predeterminado antes del punto decimal es 1, por lo que solo se guardan los dígitos después del punto decimal , lo que ahorra el espacio de un dígito, con 32 dígitos Por ejemplo, aunque solo quedan 23 bits para M, equivale a guardar 24 cifras significativas 

Almacenamiento del dígito significativo E:

 Primero, E es un número sin signo. Si E tiene 8 bits, su rango de valores es 0-255; si E tiene 11 bits, su rango de valores es 0-2047. Al almacenar el valor real de E, se debe agregar un valor intermedio . Para E de 8 bits, el valor intermedio es 127. Para E de 11 bits, el valor intermedio es 1023.

Por ejemplo: la E de 2^10 es 10, por lo que al guardar un número de coma flotante de 32 bits, se debe guardar como 10+127=137, es decir, 10001001.

 El índice E se puede dividir en tres casos cuando se saca de la memoria:

(1) E no es todo 0 o no todo 1:

Resta 127 (o 1023) del valor calculado del índice E para obtener el valor real y luego suma 1 delante del punto decimal M.

p.ej:

El valor binario de 0,5 es 0,1 y la representación del número de punto flotante es: 1,0*2^(-1), E se almacena como -1+127=126, que es 01111110, y la mantisa 1,0 menos 1 es 0, entonces la representación binaria de 0,5 es:

0 01111110 000000000000000000000000

(2) 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.

En este momento, M no necesita agregar 1 delante del punto decimal, sino que lo restaura a un decimal de 0.xxxx, lo que se hace para representar más o menos 0 y números muy pequeños cercanos a 0.

(3) 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)


 Ahora volvamos a explicar el ejemplo anterior:

De los 9 de tipo int:

intn=9;

00000000 00000000 00000000 00001001 (tipo int binario)

Pero cuando convierte el tipo en flotante*, el significado de la representación es diferente

0 00000000 00000000000000000001001

En este momento, E es todo 0, entonces E = -126, M no necesita sumar 0, es decir, M = 0.00000000000000000001001, S = 0

Entonces *pFloat es (-1)^0*0.00000000000000000001001*2^(-126), este número es extremadamente pequeño y se imprimirá directamente como 0.000000 (el valor flotante imprime 6 dígitos después del punto decimal)

Desde la perspectiva del tipo flotante 9.0: (cuando *pFloat=9.0 y posterior)

9,0 (1001,0)

Representación en coma flotante: (-1)^0*1.001*2^3

Representación binaria: 0 10000010 00100000000000000000

Luego imprima en forma de% d: desde la perspectiva de n, este es el código de complemento, el bit de signo es 0, que es un número positivo, el código de complemento inverso original es el mismo y convertido a decimal es 1091567616


Este es el final del contenido por este momento. Bienvenido al área de comentarios o comunicación por mensaje privado. Creo que la escritura del autor está bien, o he ganado un poco. Por favor, mueva sus manitas y deme un uno- haga clic en el enlace triple ¡Muchas gracias! 

Supongo que te gusta

Origin blog.csdn.net/qq_73017178/article/details/132084961
Recomendado
Clasificación