Juega con la gestión dinámica de la memoria y el desarrollo de la memoria del programa——[lenguaje C]

Hemos aprendido algunos métodos de desarrollo de memoria antes, como usar int float double, etc., así como varios tipos de arreglos. Estos pueden abrir espacio en la memoria. Pero los espacios que abren están todos muertos, y no se pueden cambiar a voluntad después de abrirlos, lo cual es muy inconveniente. Hoy vamos a aprender algunas formas nuevas de abrir la memoria: desarrollo dinámico de la memoria.


 Tabla de contenido

1. ¿Por qué existe la asignación de memoria dinámica?

2. Introducción a las funciones de memoria dinámica

malloc y gratis 

 función calloc

 función de registro

3. Errores comunes de memoria dinámica

3.1 Operación de desreferenciación en puntero NULL

3.2 Acceso fuera de límites a espacios asignados dinámicamente

3.3 Uso libre para liberar memoria no dinámica

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

3.5 Múltiples liberaciones de la misma memoria dinámica

 3.6 Abre la memoria dinámicamente y olvida liberarla (pérdida de memoria)

4. Asignación de memoria del programa C/C++ 

5. Matriz flexible 

5.1 Características de los arreglos flexibles:

5.2 Uso de arreglos flexibles 

5.3 Ventajas de los arreglos flexibles


1. ¿Por qué existe la asignación de memoria dinámica?

Los métodos de desarrollo de memoria que hemos dominado son:

int val = 20; // Permitir cuatro bytes en el espacio de pila

char arr[10] = {0};//Abrir 10 bytes de espacio continuo en el espacio de la pila 

Sin embargo, la forma de apertura del espacio antes mencionada tiene dos características:

1. El tamaño de asignación de espacio es fijo.

2. Al declarar una matriz, se debe especificar la longitud de la matriz y la memoria que necesita se asignará en el momento de la compilación.

Pero la demanda de espacio no es sólo la situación anterior. A veces, el tamaño del espacio que necesitamos solo se puede saber cuando el programa se está ejecutando, y la forma de crear espacio al compilar la matriz no es suficiente. En este momento, solo puede probar el almacenamiento y desarrollo dinámicos. ¡El punto popular es que puedes desarrollar todo lo que quieras! ! !


2. Introducción a las funciones de memoria dinámica

malloc y gratis 

El lenguaje C proporciona una función de asignación de memoria dinámica. Primero echemos un vistazo al prototipo de la función: 

Esta función se aplica a un espacio disponible contiguo de la memoria y devuelve un puntero a este espacio.

Si la asignación es exitosa, se devuelve un puntero al espacio asignado.

Si falla la apertura, 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 conoce el tipo de espacio abierto, y el usuario puede decidir por sí mismo cuándo usarlo.

Si el tamaño del parámetro es 0, el comportamiento de malloc no está definido por el estándar y depende del compilador. 

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

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 abre 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. 

Por lo tanto, el espacio al que apunta ptr es generalmente un puntero abierto por una función de memoria dinámica.

int main()
{
	int a = 10;
	int* p = &a;
	free(p);

	return 0;
}

El código anterior es incorrecto, no lo intentes a la ligera, ¡hará que el programa se bloquee! ! ! 

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

Sigamos un procedimiento:

int main()
{
	int arr[10];
	int* p = (int*)malloc(40);
	return 0;
}

Cuando queremos solicitar una matriz de enteros, generalmente usamos int arr[10]; para definir, pero también podemos usar la función malloc para abrir una memoria de 40 bytes. Solo necesitamos dar el tamaño de bytes que queremos solicitar en el parámetro. La función malloc que queremos debe acceder a cuatro bytes a la vez como una matriz, podemos asignar la dirección devuelta por malloc al puntero int* p, pero malloc devuelve un puntero de tipo void*, debemos convertirlo en int *.

Usamos malloc para imitar matrices para la aplicación:

int main()
{
	int num = 0;
	scanf("%d", &num);
	int* ptr = NULL;
	ptr = (int*)malloc(num * sizeof(int));
	if (NULL != ptr)//判断ptr指针是否为空
	{
		int i = 0;
		for (i = 0; i < num; i++)
		{
			printf("%d ", *(ptr + i));
		}
	}
	free(ptr);//释放ptr所指向的动态内存
	ptr = NULL;//是否有必要?
	return 0;
}

Cuando usamos la función malloc para abrir espacio, debemos juzgar si el puntero es un puntero nulo, ¡porque malloc puede no abrir espacio!

Cuando hayamos terminado de usar el espacio dinámico, debemos usar la función libre para liberarlo, de lo contrario, esta memoria siempre existirá a menos que se apague la computadora o finalice el programa. !

Cuando liberamos el espacio, el puntero ptr es inútil y se convierte en un puntero salvaje. Así que finalmente debemos inicializar el puntero. 

(Estos contenidos siguen siendo prácticos en las funciones calloc y realloc, por lo que debemos prestar atención)

Si malloc se abre con éxito, podemos usar el puntero para imprimir lo que hay en el espacio:

 Cuando abrimos 20 espacios, podemos acceder al contenido de 5 elementos. Todos son contenido aleatorio, lo que puede probar que el espacio abierto por la función malloc no está inicializado. 

Cuando fallamos en el desarrollo, podemos usar la función perror para generar la causa de la falla, de modo que podamos comprender mejor dónde está el problema.Podemos modificar el módulo en el que juzgamos que el puntero está vacío de la siguiente manera:

int main()
		{
			int* ptr = NULL;
			ptr = (int*)malloc(INT_MAX);
			if (ptr == NULL)
			{
				perror("malloc");
				return 1;
			}
			else if (NULL != ptr)//判断ptr指针是否为空
			{
				int i = 0;
				for (i = 0; i < INT_MAX; i++)
				{
					printf("%d ", *(ptr + i));
				}
			}
			free(ptr);//释放ptr所指向的动态内存
			ptr = NULL;//是否有必要?
			return 0;
		}

Cuando escribimos el byte de creación en INT_MAX, no hay suficiente memoria para abrir y se devuelve un puntero nulo. 

 función calloc

El lenguaje C también proporciona una función llamada calloc, que también se usa para la asignación dinámica de memoria. El prototipo es el siguiente:

La función de la función es abrir un espacio para num elementos cuyo tamaño es 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. 

 Tomemos un ejemplo:

#include <stdio.h>
#include <stdlib.h>
int main()
{
 int *p = (int*)calloc(10, sizeof(int));
 if(NULL != p)
 {
 //使用空间
}
 free(p);
 p = NULL;
 return 0;
}

A través de la depuración del programa, podemos ver que todos se inicializan a 0, que es la diferencia con la función malloc.


 función de registro

Lo que acabo de decir para hacer que la asignación de memoria sea flexible no se refleja en las dos funciones anteriores, pero se reflejará completamente en la función realloc.

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 que solicitamos en el pasado es demasiado pequeño, y a veces sentimos que el espacio que solicitamos es demasiado grande Para que la memoria sea razonable, debemos hacer ajustes flexibles al tamaño de la memoria. Luego, la función realloc puede ajustar el tamaño de la memoria asignada dinámicamente.

El prototipo de la función es el siguiente: 

ptr es la dirección de memoria para ajustar

tamaño Nuevo tamaño después del ajuste

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.

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

Caso 1: hay suficiente espacio después del espacio original

Caso 2: No hay suficiente espacio después del espacio original

 Caso 1: cuando es el caso 1, si desea expandir la memoria, puede agregar espacio directamente después de la memoria original, y los datos en el espacio original no cambiarán.

Caso 2: En el caso 2, si no hay suficiente espacio después del espacio original, el método de extensión es: encontrar otro espacio continuo de tamaño adecuado en el espacio de almacenamiento dinámico para su uso. Esta función devuelve una nueva dirección de memoria. Debido a las dos situaciones anteriores, se debe prestar cierta atención al uso de la función de reasignación. Cuando realloc abre un nuevo espacio, copiará los datos del antiguo espacio al nuevo espacio y liberará el antiguo espacio, y devolverá la dirección del nuevo espacio

 Por ejemplo:

#include <stdio.h>
int main()
{
 int *ptr = (int*)malloc(100);
 if(ptr != NULL)
 {
     //业务处理
 }
 else
 {
     exit(EXIT_FAILURE);    
 }
 //扩展容量
 //代码1
 ptr = (int*)realloc(ptr, 1000);//这样可以吗?(如果申请失败会如何?)

 //代码2
 int*p = NULL;
 p = realloc(ptr, 1000);
 if(p != NULL)
 {
 ptr = p;
 }
 //业务处理
 free(ptr);
 return 0;
}

Cuando usamos malloc para abrir espacio no es suficiente, usamos realloc para aumentar el espacio. Esto debe verse en dos casos. Cuando el espacio original es lo suficientemente grande, puede agregar directamente espacio adicional detrás y volver a la dirección original; si el espacio no es suficiente, se abrirá un nuevo espacio lo suficientemente grande y se devolverá la dirección del nuevo espacio. Si usa el código 1 para operar y siempre usa la misma variable de puntero para mantener, no importa si la apertura es exitosa, pero si la apertura falla, realloc devolverá un NULL a ptr, luego la variable creada con malloc antes tiene no hay puntero para encontrar, lo que resulta en la pérdida de datos, y no hay forma de liberar el espacio abierto por malloc, ¡causará graves consecuencias! ! ! ¡Entonces deberíamos crear una nueva variable de puntero para recibir el puntero devuelto por realloc (escrito en el código 2)!

Cuando usamos las tres funciones anteriores, debemos prestar atención a los puntos que acabamos de enfatizar para evitar errores.


3. Errores comunes de memoria dinámica

3.1 Operación de desreferenciación en puntero NULL

El primer tipo de problema es el error más común cuando usamos la función de memoria dinámica, es decir, no juzgar el valor de retorno de la función (si es NULL), para desreferenciar directamente el puntero receptor. El problema surge al desreferenciar un puntero nulo:

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

El código anterior definitivamente fallará al abrirse porque no hay suficiente espacio, dando p=NULL; por lo tanto, eliminar la referencia de p provocará un error, ¡así que debemos juzgar el puntero recibido al usar el desarrollo de memoria dinámica! ! !

3.2 Acceso fuera de límites a espacios asignados dinámicamente

Usamos un programa para entender este problema:

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

Cuando usamos malloc para abrir 40 bytes de espacio, cuando usamos el bucle for para acceder al valor en el espacio, cuando el bucle i=10, el acceso ha estado fuera de los límites. Asumiendo que se accede a i=10, es equivalente a acceder a 44 bytes de espacio, lo que excede el espacio abierto. Esto es similar a una matriz.Si un acceso fuera de los límites puede modificar otros contenidos del área del montón, ¡esto es muy peligroso! ! !


3.3 Uso libre para liberar memoria no dinámica

Esto ya lo mencioné en el módulo malloc para liberar la memoria asignada dinámicamente, ¡lo que hará que el programa se bloquee directamente!

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


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

Cuando usamos el puntero devuelto por la función de memoria dinámica, cuando movemos el puntero, la posición a la que apunta el puntero después del uso ya no es la primera dirección de la memoria abierta.Si usamos directamente este puntero para liberarlo, lo soltaremos ¡Es parte del desarrollo dinámico de la memoria!

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

En este momento, el programa fallará, por lo que cuando usamos este puntero, es mejor crear otra variable de puntero para operar y evitar este error. 


3.5 Múltiples liberaciones de la misma memoria dinámica

 Este problema generalmente solo ocurre cuando el programador está confundido y libera la misma pieza de espacio abierto varias veces.

int main()
{
	int* p = (int*)malloc(40);
	if (p == NULL)
	{
		return 1;
	}
	free(p);
	free(p);
		return 0;
}

¡Este programa también fallará! ! !


 3.6 Abre la memoria dinámicamente y olvida liberarla (pérdida de memoria)

Si olvida liberar el espacio de memoria asignado dinámicamente, ¡provocará una pérdida de memoria! ! !

A continuación daré un programa más extremo:

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

Cuando volvemos a llamar a la función, usamos la función malloc para abrir un espacio de memoria y usamos el puntero p para recibir el valor de retorno. Después de usarlo, nos olvidamos de soltarlo, pero el puntero p se destruirá después de que finalice la función. , pero el bloque de memoria creado por malloc no lo hará. Después de esta función, no hay ningún puntero que apunte a este fragmento de memoria, y nunca se volverá a encontrar (al igual que el policía encubierto en la película policial, solo el señor Chen lo sabe, pero un día el señor Chen muere y nadie lo sabrá) la identidad del policía encubierto nunca más), y luego use while(1) para ingresar a un bucle infinito para que el programa nunca termine y esta parte de la memoria se filtre. ! !

Olvidarse de liberar el espacio asignado dinámicamente que ya no está en uso puede causar pérdidas de memoria.

Recuerde: El espacio asignado dinámicamente debe liberarse y liberarse correctamente 


4. Asignación de memoria del programa C/C++ 

Aquí hay una imagen para todos, podemos ver claramente la asignación de memoria:

Varias áreas de asignación de memoria de programa C/C++:

1. Área de pila (stack): Cuando se ejecuta una función, las unidades de almacenamiento de variables locales en la función se pueden crear en la pila, y estas unidades de almacenamiento se liberan automáticamente cuando finaliza la ejecución de la función. La operación de asignación de memoria de pila está integrada en el conjunto de instrucciones del procesador, que es muy eficiente, pero la capacidad de memoria asignada es limitada. El área de la pila almacena principalmente variables locales asignadas por funciones en ejecución, parámetros de función, datos de retorno, dirección de retorno, etc.

2. Heap area (heap): Generalmente, es asignado y liberado por el programador, si el programador no lo libera, puede ser reclamado por el SO al final del programa. El método de asignación es similar a una lista enlazada.

3. 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.

4. Segmento de código: almacena el código binario del cuerpo de la función (funciones miembro de clase y funciones globales).


5. Matriz flexible 

Tal vez nunca haya oído hablar del concepto de arreglos flexibles, pero existen. En C99, el último elemento de una estructura puede ser una matriz de tamaño desconocido, que se denomina miembro de matriz flexible.

Por ejemplo:

typedef struct st_type
{
 int i;
 int a[0];//柔性数组成员
}type_a;

Algunos compiladores informarán errores que no se pueden compilar, que se pueden cambiar a: 

typedef struct st_type
{
 int i;
 int a[];//柔性数组成员
}type_a;

El número de elementos de la matriz se puede dar como 0 o no.

5.1 Características de los arreglos flexibles:

Un miembro de matriz flexible en una estructura debe estar precedido por al menos otro miembro.

El tamaño de una estructura de este tipo devuelta por sizeof no incluye memoria para arreglos flexibles.

Las estructuras que contienen miembros de matriz flexible utilizan la función malloc() para la asignación dinámica de memoria, y la memoria asignada debe ser mayor que el tamaño de la estructura para adaptarse al tamaño esperado de la matriz flexible.

typedef struct st_type
{
 int i;
 int a[0];//柔性数组成员
}type_a;
printf("%d\n", sizeof(type_a));//输出的是4

Cuando calculamos el tamaño de esta estructura, aunque el miembro tiene una variable entera i y una matriz de tipo int, pero su matriz es una matriz flexible (el número de elementos es 0), podemos entender que no tiene tamaño. Así que el tamaño de esta estructura es 4.

5.2 Uso de arreglos flexibles 

//代码1
int i = 0;
type_a *p = (type_a*)malloc(sizeof(type_a)+100*sizeof(int));
//业务处理
p->i = 100;
for(i=0; i<100; i++)
{
 p->a[i] = i;
}
free(p);

Abre dinámicamente espacio para estructuras con arreglos flexibles, porque sizeof(type_a) solo puede encontrar el tamaño de arreglos no flexibles, y el tamaño del arreglo que queremos abrir es el tamaño que abrimos con malloc, y luego usamos The Se puede recibir un puntero de estructura para obtener la memoria espacial que queremos. De esta forma, el miembro de matriz flexible a es equivalente a obtener un espacio continuo de 100 elementos enteros. 


5.3 Ventajas de los arreglos flexibles

//代码2
typedef struct st_type
{
 int i;
int *p_a;
}type_a;
type_a *p = (type_a *)malloc(sizeof(type_a));
p->i = 100;
p->p_a = (int *)malloc(p->i*sizeof(int));
//业务处理
for(i=0; i<100; i++)
{
 p->p_a[i] = i;
}
//释放空间
free(p->p_a);
p->p_a = NULL;
free(p);
p = NULL;

 El código 1 y el código 2 anteriores pueden cumplir la misma función, pero la implementación del método 1 tiene dos ventajas:

El primer beneficio es: fácil liberación de memoria.

Si nuestro código está en una función para otros, realiza una asignación de memoria secundaria y devuelve la estructura completa al usuario. El usuario puede liberar la estructura llamando gratis, pero el usuario no sabe que los miembros de esta estructura también deben estar libres, por lo que no puede esperar que el usuario lo descubra. Por lo tanto, si asignamos la memoria de la estructura y la memoria requerida por sus miembros al mismo tiempo, y devolvemos un puntero a la estructura al usuario, el usuario puede liberar toda la memoria haciendo una vez libre.

La segunda ventaja es: esto favorece la velocidad de acceso

La memoria contigua es beneficiosa para mejorar la velocidad de acceso y reducir la fragmentación de la memoria. 

 Los dos códigos anteriores colocan toda la memoria asignada en el área del montón para reducir la diferencia y lograr un propósito más similar. De hecho, no hay necesidad de esto. En el código 2, simplemente abra un espacio para el objeto puntero en la estructura.

Lo anterior es todo el contenido de este número. Espero que puedan señalar mis deficiencias en el área de comentarios. ¡Aprender con humildad es mi creencia! ! !

Supongo que te gusta

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