Capítulo de condensación del "Registro del volumen C": los cuatro hermanos de la gestión dinámica de la memoria en la primera batalla

En la antigüedad, no había nadie, y Gubiao Lingyundao era un amigo. La espada es mi vida, y es a la vez loca y caballeresca.

 

contenido

⭐1 Los puntos principales de este capítulo

⭐2 Cuatro áreas de memoria C/C++

⭐3 Asignación de memoria dinámica

2.1 ¿Qué es la asignación de memoria dinámica?

2.2 Por qué existe la asignación de memoria dinámica

4. Función de asignación de memoria dinámica

4.1 malloc

 4.2 gratis

4.3calloc

4.4 reasignar

5. Errores comunes de memoria dinámica

5.1 Desreferenciando un puntero NULL

5.2 Acceso fuera de límites a espacios desarrollados dinámicamente

5.3 Usando libre para memoria no dinámica

5.4 Use free para liberar parte de una memoria asignada dinámicamente

5.5 Liberar la misma pieza de memoria dinámica varias veces

5.6 Abre la memoria dinámicamente y olvídate de liberar (pérdida de memoria)

⭐6 Preguntas clásicas de prueba escrita

6.1 Tema 1

6.2 Tema 2

6.3 Tema 3

6.4 Tema 4


⭐ 1. Los puntos principales de este capítulo

  1. Introducción a las cuatro áreas de la memoria C/C++
  2. ¿Qué es la asignación de memoria dinámica?
  3. Por qué existe la asignación de memoria dinámica
  4. Cuatro hermanos de función de memoria dinámica: malloc, calloc, realloc, free
  5. Resumir errores comunes de memoria dinámica
  6. Análisis de varias preguntas clásicas de exámenes escritos

⭐ 2. Cuatro áreas de memoria C/C++

Como todos sabemos, la memoria se utiliza para almacenar los datos cuando el programa se está ejecutando.Para facilitar la gestión de estos datos, la memoria se divide en cuatro áreas, a saber, el área de pila , el área de montón , el segmento de datos , y el segmento de código .

 área de pila:

Área de pila (stack): al ejecutar una función, las unidades de almacenamiento de variables locales en la función se pueden crear en la pila y la función se ejecuta
Estas unidades de almacenamiento se liberan automáticamente al final. La operación de asignación de memoria de pila está integrada en el conjunto de instrucciones del procesador, que es muy eficiente, pero
Si la capacidad de memoria asignada es limitada. El área de la pila almacena principalmente variables locales, parámetros de funciones, datos de retorno,
dirección de retorno, etc
Características del área de pila:
Generalmente, el ciclo de vida y el alcance de las variables de la pila son cortos y solo existen en el marco de la pila de la función (cuerpo de la función).Cuando se destruye el marco de la pila de la función (la función regresa), el espacio al que pertenece la variable también se destruye. Por tanto, la función generalmente no devuelve la dirección de su variable local, porque ese espacio se destruye cuando la función vuelve, y volver a acceder a él es un acceso ilegal a la memoria.
(Nota: el marco de la pila de funciones es un espacio especialmente abierto por la función en el área de la pila).

Área de montón:

Esta área es asignada y liberada por el propio programador, si el espacio asignado no es liberado, será liberado por el sistema operativo al final del programa.

Características del área de montón:

 El espacio es asignado y liberado por el programador. Los datos en el área del montón solo pueden ser liberados por el programador o el sistema operativo cuando finaliza el programa. Por lo tanto, para algunos datos que desea guardar durante más tiempo, generalmente es colocado en el área de almacenamiento dinámico, y la capacidad del área de almacenamiento dinámico es mayor que la de la pila. El área es mayor.

Segmento de datos:

El segmento de datos (área estática) (estático) almacena variables globales y datos estáticos. Liberado por el sistema después de que finaliza el programa.

Características del segmento de datos:

Almacene variables globales y variables estáticas, también conocidas como área global/área estática. El ciclo de vida y el alcance de las variables globales son generalmente todo el programa. Una variable estática es una variable modificada por estática. Cuando una variable local es modificada por estática, se coloca en el segmento de datos. En este momento, su ciclo de vida se vuelve más largo. , pero el alcance permanece sin cambios. Estático La modificación de la variable global limitará el alcance de la variable global. En este momento, su alcance se convierte en el archivo actual, pero el ciclo de vida permanece sin cambios.

Fragmento de código:

Segmento de código: código binario que almacena constantes/cuerpos de función de solo lectura (funciones miembro de clase y funciones globales).

Características del fragmento de código:

El código binario del cuerpo de la función se almacena, y también se almacenan algunas constantes de caracteres.Esta área está cerca del área del segmento de datos.

Resumir:

En cuanto a las cuatro áreas de la memoria, basta con entenderlas en esta etapa, no es necesario profundizar demasiado, es solo para facilitar nuestra comprensión y aprendizaje, la distribución real de la memoria no es así.

⭐ 3. Asignación de memoria dinámica

2.1 ¿Qué es la asignación de memoria dinámica?

Enciclopedia de Baidu : la llamada asignación de memoria dinámica (Dynamic Memory Allocation) se refiere al método de asignación o recuperación dinámica de espacio de almacenamiento en el proceso de ejecución del programa. La asignación de memoria dinámica no requiere una asignación previa de espacio de almacenamiento como matrices y otros métodos de asignación de memoria estática, pero el sistema la asigna en tiempo real de acuerdo con las necesidades del programa, y ​​el tamaño de la asignación es el tamaño requerido por el programa.

2.2 Por qué existe la asignación de memoria dinámica

Porque a veces necesitamos aumentar dinámicamente nuestra capacidad de espacio, por ejemplo, cuando escribimos una libreta de direcciones, solicitamos 100 espacios consecutivos en el área de la pila, entonces este espacio no se puede expandir ni reducir, por lo que también se llama memoria estática. , y la asignación de memoria dinámica puede resolver este problema, primero podemos solicitar 10 espacios en el área del montón y luego, cuando la función de detección verifique que el espacio está lleno, la capacidad aumentará automáticamente. La llamada memoria dinámica significa que la memoria de esta aplicación se puede ampliar o reducir, por lo que esta función suele utilizarse en el aspecto de listas enlazadas.

4. Función de asignación de memoria dinámica

4.1 malloc

Prototipo de función:
void* malloc ( tamaño_t tamaño );
Esta función solicita un espacio libre contiguo del montón y devuelve un puntero a este espacio.
  • Si la apertura tiene éxito, se devuelve un puntero al espacio abierto.
  • Si el desarrollo falla, se devuelve un puntero NULL, por lo que se debe comprobar el valor de retorno de malloc.
  • El tipo del valor devuelto es void*, por lo que la función malloc no sabe el tipo de espacio que se va a abrir y el usuario decide usa.
  • Si el tamaño del parámetro es 0, el comportamiento de malloc no está definido por el estándar y depende del compilador.

 4.2 gratis

El lenguaje C proporciona otra función gratuita, que se utiliza especialmente para la liberación y recuperación de memoria dinámica, el prototipo de la función es el siguiente:

Prototipo de función:

vacío libre ( vacío * ptr );

La función libre se utiliza para liberar la memoria asignada dinámicamente.
  • Si el espacio al que apunta el parámetro ptr no se asigna dinámicamente, el comportamiento de la función libre no está definido.
  • Si el parámetro ptr es un puntero NULL, la función no hace nada.

Tanto malloc como free se declaran en el archivo de encabezado stdlib.h.  

por ejemplo:

#include <stdio.h>
int main()
{
	int* ptr = NULL;
	ptr = (int*)malloc(10 * sizeof(int));
	if (NULL != ptr)//判断ptr指针是否为空
	{
		//将堆区开辟的10个整形数据都置为0
		int i = 0;
		for (i = 0; i < 10; i++)
		{
			*(ptr + i) = 0;
		}
	}
	free(ptr);//释放ptr所指向的动态内存
	ptr = NULL;//是否有必要?
	return 0;
}

Cuando está libre (ptr), se recomienda establecer ptr en NULL; de lo contrario, ptr es un puntero salvaje y existe un riesgo oculto.

4.3calloc

El lenguaje C también proporciona una función llamada calloc , que también se usa para la asignación dinámica de memoria.
Prototipo de función:
void* calloc ( size_t num , size_t size );
  •  La función de la función es abrir un espacio para num elementos de tamaño e inicializar cada byte del espacio a 0.

  • La única diferencia con la función malloc es que calloc inicializará cada byte del espacio solicitado en ceros antes de devolver la dirección.

4.4 reasignar

Prototipo de función:

void* realloc ( void* ptr , size_t size );

  • ptr es la dirección de memoria para ajustar
  • nuevo tamaño después del ajuste de tamaño
  • El valor devuelto es la posición inicial de la memoria ajustada.
  • Sobre la base de ajustar el tamaño del espacio de memoria original, esta función también moverá los datos en la memoria original al nuevo espacio.
  • La aparición de la función realloc hace que la gestión dinámica de la memoria sea más flexible.
  • A veces encontramos que el espacio solicitado en el pasado es demasiado pequeño, y a veces sentimos que el espacio solicitado es demasiado grande.Para usar la memoria de manera razonable, debemos ajustar el tamaño de la memoria de manera flexible. La función realloc puede ajustar el tamaño de la memoria asignada dinámicamente.

Hay dos situaciones en las que realloc ajusta el espacio de memoria:

1. Expansión in situ

Agregue espacio directamente desde la parte posterior de la memoria, los datos originales no cambian.

Diagrama:

 

2. Expansión remota

Debido a que el espacio que se agregará más tarde es demasiado grande, el área del montón no es suficiente para continuar abriendo espacio más adelante (el espacio puede ser ocupado por otras variables más adelante), por lo que solo podemos encontrar un nuevo espacio de memoria grande, copiar el original datos al nuevo espacio después de encontrarlo y devolver la dirección de inicio del nuevo espacio.

Diagrama:

 

5. Errores comunes de memoria dinámica

5.1 Desreferenciando un puntero NULL

void test()
{
 int *p = (int *)malloc(INT_MAX/4);
 *p = 20;//如果p的值是NULL,就会有问题
 free(p);
}

 Después de abrir el espacio de almacenamiento dinámico, verifique si el valor de retorno está vacío, de lo contrario, es posible que no se abra, y devolver NULL causará el problema posterior de desreferencia del puntero nulo.

5.2 Acceso fuera de límites a espacios desarrollados dinámicamente

void test()
{
	int i = 0;
	int* p = (int*)malloc(10 * sizeof(int));
	if (NULL == p)
	{
		exit(EXIT_FAILURE);
	}
	for (i = 0; i <= 10; i++)
	{
		*(p + i) = i;//当i是10的时候越界访问
	}
	free(p);
}

 *(p + i) = i;//Acceso fuera de límites cuando i es 10

5.3 Usando libre para memoria no dinámica

void test()
{
	int a = 10;
	int* p = &a;
	free(p);//ok?
}

Aquí a es una variable en el área del montón, que no pertenece a la memoria abierta por el área del montón, y no se puede liberar usando libre, de lo contrario se informará de un error. 

5.4 Use free para liberar parte de una memoria asignada dinámicamente

void test()
{
	int* p = (int*)malloc(100);
	p++;
	free(p);//p不再指向动态内存的起始位置
}

Una parte del espacio abierto no se libera o se libera todo, no solo una parte.

5.5 Liberar la misma pieza de memoria dinámica varias veces

void test()
{
	int* p = (int*)malloc(100);
	free(p);
	free(p);//重复释放
}

Este problema general de copia profunda y copia superficial en C++ es la liberación repetida de la memoria del montón, lo que informará un error. 

5.6 Abre la memoria dinámicamente y olvídate de liberar (pérdida de memoria)

void test()
{
	int* p = (int*)malloc(100);
	if (NULL != p)
	{
		*p = 20;
	}
}
int main()
{
	test();
	while (1);
}

Después de usar la memoria del montón, asegúrese de liberar el espacio, de lo contrario, se producirá una fuga de memoria, lo cual es un asunto muy grave. En la empresa, esto es un accidente y la bonificación de fin de año puede cancelarse. Algunas personas pueden decir: después de que se ejecute el programa, ¿el sistema operativo no recuperará automáticamente el espacio de almacenamiento dinámico? Pero, ¿y si ese programa funciona las 24 horas del día? Esto es algo muy serio.

⭐ 6. Preguntas clásicas de prueba escrita

6.1 Tema 1

void GetMemory(char* p) {
	p = (char*)malloc(100);
}
void Test(void) {
	char* str = NULL;
	GetMemory(str);
	strcpy(str, "hello world");
	printf(str);
}
¿Cuál es el resultado de ejecutar la función Test ?
analizar:
  • No imprimirá hola mundo, porque GetMemory(str) no cambiará str, y str sigue siendo NULL, por lo que al llamar a strcpy(), habrá un problema de desreferencia de NULL. Para esto, necesita conocer la implementación de simulación de strcpy , específicamente puede consultar mi artículo anterior.
  • Dado que no se libera el espacio solicitado por el área del montón, se producirá una fuga de memoria.

6.2 Tema 2

char* GetMemory(void) {
	char p[] = "hello world";
	return p;
}
void Test(void) {
	char* str = NULL;
	str = GetMemory();
	printf(str);
}

analizar:

  • Imprimirá hola mundo, después de str=GetMemory(), str apunta al espacio donde se almacena "hola mundo".
  • Lo que debe analizarse aquí es que la primera dirección de la cadena pasada por "hola mundo" se le da a p, y la cadena es una constante de cadena, que generalmente se coloca en el área de constantes de caracteres de la memoria.
  • Sin embargo, si no se libera el espacio en el área del montón, habrá una pérdida de memoria.

6.3 Tema 3

void GetMemory(char** p, int num) {
	*p = (char*)malloc(num);
}
void Test(void) {
	char* str = NULL;
	GetMemory(&str, 100);
	strcpy(str, "hello");
	printf(str);
}

analizar:

  • Imprimirá hola, donde GetMeory() pasa la dirección de str, la dirección del puntero secundario, *p es str en sí mismo, y luego str apunta al espacio de 100 caracteres del área del montón. A través de la función strcpy(), hello puede copiarse en el espacio de almacenamiento dinámico al que apunta str.
  • Aquí se requiere el conocimiento de punteros secundarios y el principio de implementación de strcpy, para esto puedes ver mi artículo anterior, donde hay un análisis muy claro.
  • Del mismo modo, sin free(str), habrá pérdidas de memoria.

6.4 Tema 4

void Test(void)
{
	char* str = (char*)malloc(100);
	strcpy(str, "hello");
	free(str);
	if (str != NULL)
	{
		strcpy(str, "world");
		printf(str);
	}
}

analizar:

  • World no se imprimirá, str apunta al espacio de 100 caracteres del área del montón, strcpy(str, "hola"), el espacio del montón se pone en "hola", free(str), el espacio del montón se libera y str todavía apunta a este tiempo En ese espacio de almacenamiento dinámico, str es un puntero salvaje, lo cual es muy peligroso.
  • La siguiente declaración de juicio if, str no es igual a NULL, ejecute la declaración condicional y luego copie "mundo" en el espacio de almacenamiento dinámico, pero debido a que el espacio de almacenamiento dinámico se libera por adelantado, habrá un conflicto de acceso de lectura y escritura de memoria. .
  • Esto muestra que cuando free(str), str=NULL es muy necesario.

Supongo que te gusta

Origin blog.csdn.net/m0_62171658/article/details/123514730
Recomendado
Clasificación