Operaciones de memoria dinámica (2)

Continuando con el artículo anteriorhttp ://t.csdn.cn/1ONDq , Esta vez continuamos explicando los conocimientos relevantes sobre la memoria dinámica.

1. Errores comunes de memoria dinámica

1. Desreferenciar el puntero NULL

#include<stdio.h>
#include<stdlib.h>
#include<limits.h>
int main()
{
	int* p = (int*)malloc(INT_MAX/4);
	*p = 20;//如果没有足够的空间导致p为NULL,就会有问题
	//所以必须对malloc的返回值进行判断
	free(p);
	p = NULL;
	return 0;
}

2. Acceso fuera de los límites al espacio abierto dinámicamente

int main()
{
	int* p = (int*)malloc(40);
	if (p == NULL)
	{
		perror("malloc");
		return 1;
	}
	int i = 0;
	for (i = 0; i < 11; i++)
	{
		p[i] = 0;
		//原本只申请了十个整型的空间,但却访问十一个整型
		//所以造成越界访问
	}
	free(p);
	p = NULL;
	return 0;
}

3. Utilice la versión gratuita para memoria asignada de forma no dinámica


int main()
{
	int a = 0;
	int* p = &a;
	free(p);//p不是动态开辟的空间,不能释放
	p = NULL;
	return 0;
}

4. Utilice gratis para liberar una parte de la memoria asignada dinámicamente.

int main()
{
	int* p = (int*)malloc(40);
	if (p == NULL)
	{
		perror("malloc");
		return 1;
	}
	int i = 0;
	for (i = 0; i < 5; i++)
	{
		p[i] = 0;
		p++;
	}
	//p++导致p不再指向这块空间的起始地址
	//所以如果释放p,等于释放这块空间的一部分(后五个整型空间)
	//这样就会出问题
	free(p);
	p = NULL;
	return 0;
}

5. Libere la misma memoria dinámica rápida varias veces

int main()
{
	int* p = (int*)malloc(40);
	if (p == NULL)
	{
		perror("malloc");
		return 1;
	}
	free(p);
	//。。。。。
	free(p);
	//有时候头脑不清醒就可能释放多次,这样就会出问题
	return 0;
}

6. Olvidarse de liberar memoria después de asignarla dinámicamente (el más común)

Es decir, después de solicitar memoria dinámicamente, finalmente nos olvidamos de usar free para liberarla, lo que provocará una pérdida de memoria .

2. Varios ejemplos clásicos sobre memoria dinámica.

Ejemplo 1. ¿Cuál es el resultado de ejecutar el código?

código fuente:

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

int main()
{
	Test();
	return 0;
}

Pregunta: ¿Cuál será el resultado de ejecutar este código?

Respuesta al ejemplo 1:

En primer lugar, la memoria dinámica no se libera;

En segundo lugar, se producirá este error:

La razón es: debido a que en la función de prueba, el puntero str se establece en nulo y luego se pasa como parámetro a la función GesMemory. El parámetro formal de la función lo recibe el puntero p, por lo que el puntero p también es NULL. y luego el espacio se abre dinámicamente para el puntero p, y la función finaliza. Vaya a la función strcpy, pero notamos que estamos abriendo espacio para el puntero p, pero el puntero str no ha cambiado. Muchos socios no pueden descúbrelo. ¿No es esto una llamada por dirección? Si el puntero p cambia, ¿el puntero str también debería cambiar?

En realidad no es así, hay que prestar atención a que el parámetro es un puntero y no necesariamente es una llamada por dirección, aquí hay una asignación entre punteros, que se debe subir a un nivel al mismo tiempo. Aquí se requiere un puntero de segundo nivel para contar como una llamada por dirección, por lo que el puntero str no cambiará. Sigue siendo NULL. Como es NULL, no hay suficiente espacio para colocar el segundo parámetro de strcpy, por lo que se produce un error. esta reportado.

Ejemplo 2. ¿Cuál es el resultado de ejecutar el código?

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

int main()
{
	Test();
	return 0;
}

Pregunta: ¿Cuál es el resultado de ejecutar este código?

Respuesta al ejemplo 2:

producirá este resultado:

"Muchos amigos pueden pensar que la dirección inicial p de la cadena se devuelve en la función GetMemory, por lo que el puntero str se usa en la función de Prueba para recibirla e imprimirla, por lo que el resultado de la ejecución debe ser una cadena impresa".

Pero este no es el caso, debemos prestar atención al ciclo de vida de cada variable . El ciclo de vida del array p está solo en la función GetMemory, por lo que cuando la función regrese, el espacio ocupado por las variables en su interior se destruirá automáticamente. (liberado). Dado que el espacio de p ha sido liberado y asignado al puntero str, entonces str es un puntero salvaje . Si str se imprime nuevamente, provocará un acceso ilegal a la memoria .

Este tipo de problema pertenece a: el problema de devolver la dirección del espacio de pila .

3. Asignación de memoria para programas C/C++

Como se muestra abajo:

Varias áreas de asignación de memoria para programas C/C++ :
1. Área de pila ( pila ): al ejecutar una función, las unidades de almacenamiento de las variables locales dentro de 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 y es muy eficiente, pero la capacidad de memoria asignada es limitada. El área de la pila almacena principalmente variables locales, parámetros de funciones, datos de retorno, direcciones de retorno, etc. asignados para ejecutar funciones.
2. Área de montón ( montón ): generalmente asignada y liberada por el programador. Si el programador no la libera, el sistema operativo puede reciclarla cuando finalice el programa . El método de asignación es similar a una lista vinculada.
3. El segmento de datos (área estática) ( estático ) almacena variables globales y datos estáticos. El sistema lo publica una vez finalizado el programa.
4. Segmento de código: almacena el código binario del cuerpo de la función (función miembro de clase y función global).
De hecho, las variables locales ordinarias asignan espacio en el área de la pila . La característica del área de la pila es que las variables creadas anteriormente se destruyen cuando salen del alcance. Sin embargo, las variables modificadas por estática se almacenan en el segmento de datos (área estática) . La característica del segmento de datos es que las variables creadas anteriormente no se destruyen hasta el final del programa, por lo que el ciclo de vida se vuelve más largo.

4. Matriz flexible

(1) El concepto de matriz flexible:

Quizás nunca hayas oído hablar del concepto de matrices flexibles , pero existen.
En C99 , se permite que el último elemento de una estructura sea una matriz de tamaño desconocido, lo que se denomina miembro de "matriz flexible".
como sigue:
//定义一:
struct S
{
	int a;
	int arr[0];
};
//定义二:
struct B
{
	int a;
	char b;
	int arr[];
};

(2) Características de las matrices flexibles:

1. El miembro de la matriz flexible en la estructura debe estar precedido por al menos otro miembro.
2. El tamaño de la estructura devuelta por sizeof no incluye la memoria de la matriz flexible, es decir, sizeof solo calcula el tamaño del miembro frontal de la matriz flexible.
3. Utilice la función malloc () para asignar dinámicamente memoria para estructuras que contienen miembros de matriz flexible . La memoria asignada debe ser mayor que el tamaño para adaptarse al tamaño esperado de la matriz flexible.

(3) Uso de matrices flexibles:

Por ejemplo:
struct S
{
	int a;
	int arr[];
};

int main()
{
	//动态开辟了4+40个字节,因为柔性数组是不会被sizeof计算的
	//前面四个字节是给成员a的,后面四十个字节给柔性数组
	//因为柔性数组的大小是未知的,我们只需给出预期大小
	struct S* str = (struct S*)malloc(sizeof(struct S) + 40);
	//检查
	if (str == NULL)
	{
		perror("malloc");
		return 1;
	}
	//使用
	str->a = 10;
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		str->arr[i] = i + 1;
		//printf("%d ", str->arr[i]);
	}
	//用realloc扩容,因为柔性数组大小未知,是可以改变的
	//将之前柔性数组的10个字节的大小扩容到15个
	struct S* p = (struct S*)realloc(str, sizeof(struct S) + 60);
	//检查
	if (p == NULL)
	{
		perror("realloc");
		return 1;
	}
	//使用
	str = p;
	for (i = 10; i < 15; i++)
	{
		str->arr[i] = i + 1;
	}
	//打印
	for (i = 0; i < 15; i++)
	{
		printf("%d ", str->arr[i]);
	}
	//释放
	free(str);
	str = NULL;
	return 0;
}

Utilice la función malloc para abrir espacio y la función realloc para expandir el tamaño, de modo que el tamaño de la matriz sea variable y flexible.

Sí, esta es la característica de los arreglos flexibles.

(4) Ventajas de las matrices flexibles:

1. Cómoda liberación de memoria:
Si nuestro código está en una función para que otros lo usen, usted realiza una asignación de memoria secundaria en él y devuelve la estructura completa al usuario. El usuario puede liberar la estructura llamando a free , pero no sabe que los miembros de la estructura también deben ser libres, por lo que no se puede esperar que el usuario descubra esto. Por lo tanto, si asignamos la memoria de la estructura y la memoria requerida por sus miembros a la vez, y devolvemos un puntero de estructura al usuario, el usuario puede liberar toda la memoria una vez .
2. Esto es beneficioso para la velocidad de acceso:
La memoria contigua es beneficiosa para mejorar la velocidad de acceso y reducir la fragmentación de la memoria. (En realidad, personalmente no creo que sea mucho más alto. De todos modos, no se puede ejecutar y hay que usar la suma compensada para la dirección).

Supongo que te gusta

Origin blog.csdn.net/hffh123/article/details/133349260
Recomendado
Clasificación