Acerca de la asignación de memoria dinámica
Recuerde los métodos de asignación de memoria que aprendimos antes:
int val = 20;//在栈空间上开辟四个字节
char arr[10] = {
0};//在栈空间上开辟10个字节的连续空间
Cuando aprendemos lenguaje C, sabemos que las estructuras de datos suelen tener un tamaño fijo. Tome la matriz como ejemplo: una vez compilado el programa, se determina el tamaño de la matriz y el número de elementos . Entonces, el tamaño de la estructura de datos no se puede cambiar sin modificar el programa y compilarlo nuevamente. El resumen son las siguientes dos características:
- El tamaño de la apertura del espacio es fijo.
- Al declarar una matriz, se debe especificar la longitud de la matriz. Una vez que se determina el tamaño del espacio de la matriz, no se puede ajustar.
Pero la demanda de espacio no se limita sólo a las situaciones antes mencionadas. A veces, la cantidad de espacio que necesitamos solo se puede saber cuando el programa se está ejecutando, por lo que el método de crear espacio durante la compilación de la matriz no es suficiente.
Entonces, el lenguaje C introdujo la asignación de memoria dinámica, que permite a los programadores solicitar y liberar espacio ellos mismos . A continuación se presentará cómo abrir memoria dinámicamente.
Introducción a las funciones malloc y calloc
La siguiente es la definición de par cplusplusmalloc
:
void* malloc (size_t size);
Esta función se aplica a un espacio continuo en la memoria y devuelve un puntero a este espacio. size
Es decir, el tamaño del espacio de memoria que desea solicitar void*
es la dirección del primer elemento de la memoria solicitada. Como no conoce el tipo, lo usa void*
. También hay que tener en cuenta los siguientes puntos:
- Si la asignación se realiza correctamente, se devuelve un puntero al espacio asignado.
- Si la asignación falla, se devolverá un puntero NULL, por lo que se debe verificar el valor de retorno de malloc.
- El tipo de valor de retorno es void*, por lo que la función malloc no sabe el tipo de espacio a abrir, el usuario debe decidir por sí mismo al utilizarlo.
- Si el parámetro de tamaño es 0, el comportamiento de malloc no está definido por el estándar y depende del compilador.
Echemos un vistazo a la definición de par cpluspluscalloc
:
void* calloc (size_t num, size_t size);
malloc
De hecho, calloc
es muy similar a. ralloc
El parámetro en size
es el tamaño de cada tipo de datos que desea solicitar, que num
es el número de tipos de datos que desea solicitar. El tamaño total de la solicitud es num*size
, de hecho. , se puede representar malloc
por size
. Las otras características son malloc
similares, por lo que no las presentaré en detalle. Por supuesto, existen diferencias
entre los dos , como sigue:
malloc
La única diferencia con la función es que cada bytecalloc
del espacio solicitado se inicializa a 0 antes de devolver la dirección .
Entonces, si queremos inicializar el espacio de memoria aplicado dinámicamente a 0, es ralloc
más conveniente usarlo.
Reciclaje de memoria dinámica ---- gratis
De hecho malloc
, calloc
funciones como la asignación dinámica de memoria en realidad asignan memoria en el área del montón. Dado que el sistema no recupera automáticamente el espacio de memoria solicitado por estas funciones , el uso de dichas funciones con demasiada frecuencia para abrir espacio provocará el agotamiento del montón. En este momento necesitamos liberar activamente el espacio abierto , por lo que introducimos free
una función, cuyo prototipo de función es el siguiente:
void free (void* ptr);
El puntero aquí ptr
apunta a nuestra primera dirección de memoria asignada dinámicamente . Solo apuntando a la primera dirección se puede liberar por completo el espacio de memoria asignado dinámicamente. Hay dos casos especiales con respecto a ptr
los punteros ;
- Si
ptr
el espacio al que apunta el parámetro no se asigna dinámicamente,free
el comportamiento de la función no está definido.- Si el argumento
ptr
esNULL
un puntero, la función no hace nada.
Dos cosas más a tener en cuenta :
- Después de liberar el espacio abierto, el puntero original que apunta a este espacio
ptr
todavía almacena la dirección aquí. Para evitar asignar o desreferenciar accidentalmente este puntero más adelante, lo que causa un problema de puntero salvaje , después de liberar el espacio, también debemos Este puntero debe ser asignadoNULL
. - Es mejor tener siempre un puntero a este espacio al escribir código. Si no hay un puntero a este espacio, entonces este espacio no será accesible ni liberado. Para los programas, el espacio inaccesible también se llama basura, y los programas que dejan basura tienen pérdidas de memoria .
Como se muestra en la figura anterior, el puntero original p
apunta al primer bloque de memoria y, después de la operación, p
apunta al segundo bloque de memoria. Entonces, dado que no hay un puntero que apunte al primer bloque de memoria, este bloque de memoria ya no se puede usar, lo cual es la basura mencionada anteriormente, lo que provoca una pérdida de memoria .
introducción a la función de reasignación
A veces encontramos que el espacio que solicitamos en el pasado es demasiado pequeño y otras veces sentimos que el espacio que solicitamos es demasiado grande. Para solicitar memoria de manera razonable, debemos hacer ajustes flexibles en el tamaño de la memoria. En este momento, realloc
la función puede ajustar el tamaño de la memoria asignada dinámicamente .
El prototipo de función es el siguiente:
void* realloc (void* ptr, size_t size);
El puntero ptr
apunta a la dirección de memoria que se ajustará size
y al tamaño de memoria ajustado. El valor de retorno es la dirección inicial de la memoria ajustada.
Caso 1: Hay suficiente espacio después del espacio original
. En el caso 1, si desea expandir la memoria, simplemente agregue espacio directamente después de la memoria original. Los datos en el espacio original no cambiarán.
Caso 2: No hay suficiente espacio después del espacio original
. Cuando es el caso 2 y no hay suficiente espacio después del espacio original, el método de expansión es: encontrar otro espacio continuo de tamaño apropiado en el espacio del montón para usar. De esta forma, la función devuelve una nueva dirección de memoria. En este caso, para los datos que ya están en la memoria original, esta función copiará esos datos a la nueva memoria y se liberará la memoria original .
También existe el problema de no poder encontrar el espacio de memoria original realloc
debido a que el puntero se asigna cuando no se puede abrir el espacio de memoria dinámica . NULL
Generalmente creamos un nuevo puntero para recibir la dirección y NULL
luego lo asignamos al puntero original después de juzgar que no es correcto, de la siguiente manera:
int main()
{
int* ptr=(int*)malloc(5*sizeof(int));
int* p=(int*)realloc(10*sizeof(int));
if(p != NULL)
ptr = p;
//......(代码)
free(ptr);
ptr = NULL;
return 0;
}
Errores comunes de memoria dinámica
- Operación de desreferenciación en puntero NULL:
void test()
{
int *p = (int *)malloc(INT_MAX/4);
*p = 20;
free(p);
}
Si el valor en este código p
es NULL
, habrá un problema.
- Acceso fuera de límites al espacio asignado 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);
}
- Utilice la versión gratuita para memoria asignada de forma no dinámica:
void test()
{
int a = 10;
int *p = &a;
free(p);
}
El de aquí a
se abre en el área de la pila. Si utiliza free
el sistema de liberación, se informará un error.
- Libere la misma memoria dinámica varias veces:
void test()
{
int *p = (int *)malloc(100);
free(p);
free(p);//重复释放
}
malloc
El espacio de memoria dinámica abierto aquí se libera repetidamente y el sistema también informará un error.
- Utilice gratis para liberar una parte de la memoria asignada dinámicamente:
void test()
{
int *p = (int *)malloc(100);
p++;
free(p);
}
Debido a que ++
el símbolo cambiará el valor de la variable, ya p
no apunta a la posición inicial de la memoria dinámica. En este momento, el uso de free
la liberación no liberará toda la memoria dinámica.
- Memoria asignada dinámicamente y olvídese de liberarla (pérdida de memoria):
void test()
{
int *p = (int *)malloc(100);
if(NULL != p)
{
*p = 20;
}
}
int main()
{
test();
while(1);
}
Después de llamar test()
a la función, el espacio de memoria abierto no se libera activamente. El mismo espacio de memoria en el área de la pila se reciclará después de llamar a esta función, por lo que int* p
no se puede encontrarmalloc
el espacio abierto. Esta es la basura mencionada anteriormente, y la basura que queda El programa tiene una pérdida de memoria . ¡ Recuerde que la memoria abierta dinámicamente debe liberarse!