[C++] Gestión de memoria

Tabla de contenido

Gestión de la memoria::

                        1. Distribución de memoria C/C++

                        2. Gestión de memoria dinámica en lenguaje C

                        3. Gestión de memoria dinámica en C++

                        4. Operador nuevo y funciones de eliminación de operador

                        5. Principio de implementación de nuevo y borrado

                        6. Localización de la nueva expresión

                        7. Fugas de memoria

                        


Gestión de la memoria::

1. Distribución de memoria C/C++

int globalVar = 1;
static int staticGlobalVar = 1;
void Test()
{
	static int staticVar = 1;
	int localVar = 1;
	int num1[10] = { 1, 2, 3, 4 };
    //注:是将常量区的"abcd"拷贝到char2数组
	char char2[] = "abcd";
	const char* pChar3 = "abcd";
	int* ptr1 = (int*)malloc(sizeof(int) * 4);
	int* ptr2 = (int*)calloc(4, sizeof(int));
	int* ptr3 = (int*)realloc(ptr2, sizeof(int) * 4);
	free(ptr1);
	free(ptr3);
}
1. 选择题:
选项 : A.栈  B.堆 C.数据段(静态区)  D.代码段(常量区)
globalVar在哪里?(C)   staticGlobalVar在哪里?(C)
staticVar在哪里?(C)   localVar在哪里?(A)
num1 在哪里?(A)

char2在哪里?(A)      *char2在哪里?(A)
pChar3在哪里?(A)     *pChar3在哪里?(D)
ptr1在哪里?(A)       *ptr1在哪里?(B)
2. 填空题:
sizeof(num1) = 40;
sizeof(char2) = 5;      strlen(char2) = 4;
sizeof(pChar3) = 4/8;     strlen(pChar3) = 4;
sizeof(ptr1) = 4/8;

ilustrar:

1. La pila también se denomina pila, que almacena variables locales no estáticas/parámetros de función/valores devueltos, etc., y la pila crece hacia abajo.

2. El segmento mapeado en memoria es un método eficiente de mapeo de E/S para cargar un banco de memoria dinámica compartida. Los usuarios pueden usar la interfaz del sistema para crear una memoria compartida compartida para la comunicación entre procesos.

3. El montón se usa para la asignación de memoria dinámica cuando el programa se está ejecutando y el montón crece hacia arriba.

4. Segmento de datos, almacenamiento de datos globales y datos estáticos

5. Segmento de código, que almacena código ejecutable o constantes de solo lectura

2. Gestión de memoria dinámica en lenguaje C: malloc/calloc/realloc/free

void Test()
{
	int* p1 = (int*)malloc(sizeof(int));
	free(p1);
	// 1.malloc/calloc/realloc的区别是什么?
	int* p2 = (int*)calloc(4, sizeof(int));
	int* p3 = (int*)realloc(p2, sizeof(int) * 10);
	// 这里不需要free(p2)
	free(p3);
}

Preguntas de entrevista:

¿La diferencia entre malloc/calloc/realloc? (Consulte el blog [Lenguaje C] Gestión de memoria dinámica y arreglos flexibles para obtener más información)

3. Gestión de memoria dinámica en C++

El método de administración de memoria del lenguaje C puede continuar utilizándose en C++, pero es impotente en algunos lugares y es más problemático de usar. Por lo tanto, C++ ha propuesto su propio método de administración de memoria, que utiliza operadores nuevos y de eliminación para la memoria dinámica. gestión.

Tipos integrados de operación nueva/eliminación

void Test()
{
	// 动态申请一个int类型的空间
	int* ptr4 = new int;

	// 动态申请一个int类型的空间并初始化为10
	int* ptr5 = new int(10);

	// 动态申请10个int类型的空间
	int* ptr6 = new int[3];
	delete ptr4;
	delete ptr5;
	delete[] ptr6;
}

 Nota: Para solicitar y liberar el espacio de un solo elemento, use los operadores new y delete, para solicitar y liberar el espacio continuo, use new[] y delete[], atención, úselos juntos.

operaciones de creación y eliminación en tipos personalizados

class A
{
public:
	A(int a = 0)
		: _a(a)
	{
		cout << "A():" << this << endl;
	}
	~A()
	{
		cout << "~A():" << this << endl;
	}
private:
	int _a;
};
int main()
{
	// new/delete 和 malloc/free最大区别是 new/delete对于自定义类型除了开空间还会调用构造函数和析构函数
	A* p1 = (A*)malloc(sizeof(A));
	A* p2 = new A(1);
	free(p1);
	delete p2;
	// 内置类型是几乎是一样的
	int* p3 = (int*)malloc(sizeof(int)); // C
	int* p4 = new int;
	free(p3);
	delete p4;
	A* p5 = (A*)malloc(sizeof(A) * 10);
	A* p6 = new A[10];
	free(p5);
	delete[] p6;
	return 0;
}

1. Use eliminar para liberar el espacio solicitado por new[]

1. Tipos incorporados

No hay problema con estas operaciones en tipos integrados como int, char, float y double.

int main()
{
	//new[]在给内置类型申请空间时,不会存储元素个数,因为自定义类型delete[]时没有析构函数可以调用,free不会出错
	//以内置类型int为例
	int* ptr = new int[10];
	delete ptr;
	return 0;
}

Como en el código anterior, después de ejecutar delete ptr, puede ver que se libera este espacio continuo.

2. Tipo personalizado

Operaciones tales como estructuras, enumeraciones, uniones en lenguaje C y clases en C++ son problemáticas.

class A
{
public:
	int _sum;
	~A()
	{
		cout << "调用析构" << endl;
	}
};
int main()
{
	A* ptr = new A[10];
	delete ptr;
	return 0;
}

Al ejecutar delete ptr, el programa solo genera una oración de "llamar al destructor" y luego termina de manera anormal.

principio:

El principio de new/new[] y delete/delete[]: new y delete realmente encapsulan dos funciones globales operator new y operator delete en la capa inferior, mientras que operator new y operator delete encapsulan malloc() y operator delete respectivamente en la parte inferior capa libre().

Por qué los tipos incorporados no fallan:

Tome el tipo int incorporado en el código anterior como ejemplo. Cuando se usa new int[10], malloc en realidad solicita espacio para 10 datos de tipo int en la capa inferior. Al eliminar, la capa inferior se libera directamente con gratis, lo que obviamente no es problema.

¿Por qué el tipo personalizado sale mal?

Tome la clase A en el código anterior como ejemplo. Cuando new A[10] se maneja de manera diferente a int, para un tipo personalizado, new llamará al constructor de la clase A después de solicitar espacio con malloc en la capa inferior. se inicializan y así sucesivamente.

Cabe señalar aquí que cuando el tipo asignado por new[] es un tipo personalizado, new[] permitirá que malloc solicite 4 bytes más al asignar espacio, y new[] devuelve la dirección de retorno del malloc subyacente hacia atrás. la dirección de 4 bytes, como se muestra en la siguiente figura:

j

Específicamente, cuando se trata de new int[10], malloc debería haber solicitado 10 espacios tipo A, es decir, 40 bytes, pero malloc solicitó 44 bytes, y el puntero devuelto por new es el puntero devuelto por malloc.4 bytes de dirección.

Estos 4 bytes almacenan el número de elementos personalizados aplicados para determinar la llamada del destructor.

Luego se dijo que free puede saber cuánto espacio liberar al pasar un puntero, entonces, ¿no es necesario agregar estos 4 bytes para almacenar la cantidad de elementos?

Es necesario hablar sobre el principio de delete[]: delete[] devolverá la dirección del puntero devuelto por new[] desplazado por 4 bytes hacia adelante para liberar, por lo que free puede liberar correctamente todo el espacio.

La coincidencia correcta de A* ptr = newA[10] es delete[]ptr, entonces el puntero obtenido por delete[] en este momento es el valor de retorno de new[] (no la primera dirección del espacio solicitado por malloc), por lo que el free subyacente no puede liberar este espacio a través de este puntero, pero delete[] también cambia la dirección del puntero devuelto por new forward en 4 bytes a free.

Los 4 bytes adicionales tienen la función de la cantidad de elementos de tipo personalizado:

Un objeto necesita llamar a un destructor para limpiar algunos recursos antes de liberar espacio, entonces surge el problema, delete[] no pasa parámetros como new A[10], ¿cómo sabe delete[] cuántas veces llamar al destructor? De hecho, la cantidad de elementos almacenados en los 4 bytes adicionales se usa para que delete[] sepa cuántas veces llamar al destructor.

2. Utilice eliminar[] para liberar el espacio solicitado por nuevos

1. Tipos incorporados

Cuando el tipo es un tipo incorporado, delete[] es lo mismo que delete, por lo que no hay error.

int main()
{
	int* ptr = new int();
	delete[] ptr;
	return 0;
}

2. Tipo personalizado

Habrá un error, la razón también se menciona arriba, cuando el tipo es un tipo personalizado, delete[], p primero determinará la cantidad de veces que llamará al destructor de acuerdo con la cantidad de elementos en los primeros 4 bytes de p , y llame a la primera función del destructor, los primeros 4 bytes de la memoria son memoria ilegal, y el valor que contiene es aleatorio, por lo que llamará al destructor muchas veces y luego dará la dirección de los primeros 4 bytes del puntero recibido p al subyacente libre, pero preste atención a los 4 bytes anteriores, estos 4 bytes no se aplican por espacio

Dado que free no puede liberar el espacio que no se ha solicitado, free cometerá un error y free no puede saber cuánto espacio liberar a través de la dirección de los primeros 4 bytes de p, y también cometerá un error (free solo puede saber qué usar a través de la dirección devuelta por malloc libera mucho espacio).

class A
{
public:
	int _sum;
	~A()
	{
		cout << "调用析构" << endl;
	}
};
int main()
{
	A* ptr = new A();
	delete[] ptr;
	return 0;
}

4. Operador nuevo y funciones de eliminación de operador

new y delete son operadores para que los usuarios apliquen y liberen memoria dinámica. operator new y operator delete son funciones globales proporcionadas por el sistema. new llama a la nueva función global del operador en la capa inferior para solicitar espacio, y delete usa el operador delete global Función en la capa inferior para liberar espacio.

operator new:该函数实际通过malloc来申请空间,当malloc申请空间成功时直接返回;申请空间
失败,尝试执行空间不足应对措施,如果改应对措施用户设置了,则继续申请,否则抛异常。
void* __CRTDECL operator new(size_t size) _THROW1(_STD bad_alloc)
{
	// try to allocate size bytes
	void* p;
	while ((p = malloc(size)) == 0)
		if (_callnewh(size) == 0)
		{
			// report no memory
			// 如果申请内存失败了,这里会抛出bad_alloc 类型异常
			static const std::bad_alloc nomem;
			_RAISE(nomem);
		}
	return (p);
}
/*
operator delete: 该函数最终是通过free来释放空间的
*/
void operator delete(void* pUserData)
{
	_CrtMemBlockHeader* pHead;
	RTCCALLBACK(_RTC_Free_hook, (pUserData, 0));
	if (pUserData == NULL)
		return;
	_mlock(_HEAP_LOCK);  /* block other threads */
	__TRY
		        /* get a pointer to memory block header */
		pHead = pHdr(pUserData);
	         /* verify block type */
	_ASSERTE(_BLOCK_TYPE_IS_VALID(pHead->nBlockUse));
	_free_dbg(pUserData, pHead->nBlockUse);
	__FINALLY
		_munlock(_HEAP_LOCK);  /* release other threads */
	__END_TRY_FINALLY
	return;
}
free的实现
#define free(p) _free_dbg(p, _NORMAL_BLOCK)

A través de la implementación de las dos funciones globales anteriores, sabemos que el operador new realmente solicita espacio a través de malloc. Si malloc solicita espacio con éxito, regresará directamente. De lo contrario, ejecutará las contramedidas proporcionadas por el usuario por espacio insuficiente. Si el usuario proporciona esta medida, seguirá aplicándose, de lo contrario arrojará anormal. operator delete finalmente libera espacio a través de free.

5. Principio de implementación de nuevo y borrado

tipo incorporado

Si solicita un tipo de espacio incorporado, new y malloc, delete y free son básicamente similares, la diferencia es: new/delete solicita y libera el espacio de un solo elemento, new[] y delete[] solicitan espacio continuo, y new generará una excepción cuando no se solicite espacio, y malloc devolverá NULL.

tipo personalizado

El principio de nuevo:

1. Llame al operador nueva función para solicitar espacio

2. Ejecute el constructor en el espacio solicitado para completar la construcción del objeto.

El principio de eliminar:

1. Ejecute el destructor en el espacio para completar la limpieza de recursos en el objeto

2. Llame a la función de eliminación del operador para liberar el espacio del objeto

El principio de la nueva T[N]:

1. Llame a la función operator new[] y, de hecho, llame a la función operator new en operator new[] para completar la aplicación de N espacios de objetos

2. Ejecuta el constructor N veces en el espacio solicitado

El principio de delete[]:
1. Ejecutar N veces de destructores en el espacio de objetos liberados para completar la limpieza de recursos en N objetos

2. Llame a operator delete[] para liberar espacio, en realidad llame a operator delete en operator delete[] para liberar espacio

6. Localización de la nueva expresión

Posicionar la nueva expresión es llamar al constructor para inicializar un objeto en el espacio de memoria original asignado.

Usa el formato:

new(place_address) type或者new(place_address) type(initializer-list)

place_address debe ser un puntero, initializer-list es la lista de inicializadores de tipo

Escenas a utilizar:

En la práctica, el posicionamiento de nuevas expresiones generalmente se usa junto con los grupos de memoria. Debido a que la memoria asignada por el grupo de memoria no se inicializa, si es un objeto de un tipo personalizado, debe usar la expresión de definición de nuevo para llamar explícitamente al constructor para la inicialización.

class A
{
public:
	A(int a = 0)
		: _a(a)
	{
		cout << "A():" << this << endl;
	}
	~A()
	{
		cout << "~A():" << this << endl;
	}
private:
	int _a;
};
// 定位new/replacement new
int main()
{
	// p1现在指向的只不过是与A对象相同大小的一段空间,还不能算是一个对象,因为构造函数没有执行
	A* p1 = (A*)malloc(sizeof(A));
	new(p1)A;  //注意:如果A类的构造函数有参数时,此处需要传参
	p1->~A();
	free(p1);
	A* p2 = (A*)operator new(sizeof(A));
	new(p2)A(10);
	p2->~A();
	operator delete(p2);
	return 0;
}

Preguntas de entrevista:

La diferencia entre malloc/free y new/delete:

Lo que tienen en común malloc/free y new/delete es que todos solicitan espacio del montón y el usuario debe liberarlos manualmente.

Las diferencias son:

1. malloc y free son funciones, new y delete son operadores

2. Para tipos personalizados, el espacio solicitado por malloc no se inicializará, pero se puede inicializar uno nuevo

3. Cuando malloc solicita espacio, debe calcular manualmente el tamaño del espacio y transferirlo. New solo necesita seguirlo con el tipo de espacio. Si hay varios objetos, especifique la cantidad de objetos en []

4. El valor de retorno de malloc es void*, que debe forzarse cuando se usa, y new no lo necesita, porque new va seguido de un tipo de espacio

5. Cuando malloc no puede solicitar espacio, se devuelve NULL, porque debe considerarse vacío cuando se usa, new no lo necesita, pero new necesita detectar excepciones

6. Al solicitar un objeto de tipo personalizado, malloc/free solo abrirá espacio para liberar el espacio y no llamará al constructor ni al destructor, mientras que new llamará al constructor para completar la inicialización del objeto después de solicitar el espacio. y delete llamará al constructor antes de liberar el espacio Llama al destructor para limpiar los recursos en el espacio

7. Fugas de memoria                  

¿Qué es una fuga de memoria y el daño de una fuga de memoria?

Qué es una fuga de memoria: Una fuga de memoria se refiere a una situación en la que un programa no puede liberar memoria que ya no está en uso debido a negligencia o errores. Una fuga de memoria no se refiere a la pérdida física de memoria, sino a la pérdida de control sobre el segmento de memoria debido a un error de diseño después de que la aplicación asigna un determinado segmento de memoria, lo que resulta en una pérdida de memoria.

Riesgos de fugas de memoria: para los programas a corto plazo, siempre que el programa finalice normalmente (ejecutar declaración de retorno 0), el sistema operativo reclamará memoria y no habrá fugas de memoria, pero los programas de ejecución prolongada tienen fugas de memoria, lo que tienen un gran impacto, como el sistema operativo, los servicios en segundo plano, etc., las fugas de memoria harán que la respuesta se vuelva cada vez más lenta y, finalmente, se atasque.

void MemoryLeaks()
{
	// 1.内存申请了忘记释放
	int* p1 = (int*)malloc(sizeof(int));
	int* p2 = new int;

	// 2.异常安全问题
	int* p3 = new int[10];

	Func(); // 这里Func函数抛异常导致 delete[] p3未执行,p3没被释放.

	delete[] p3;
}

Clasificación de las fugas de memoria:

En los programas C/C++, generalmente nos preocupamos por dos aspectos de las fugas de memoria:

Fuga de montón:

La memoria heap se refiere a una parte de la memoria asignada desde el montón a través de malloc/calloc/realloc/new de acuerdo con las necesidades de ejecución del programa.Después de su uso, debe eliminarse llamando al correspondiente free o delete. Suponiendo que el error de diseño del programa hace que esta parte de la memoria no se libere, esta parte del espacio ya no se utilizará en el futuro y se producirá una fuga de almacenamiento.

Fugas de recursos del sistema:

Se refiere a los recursos asignados por el sistema utilizados por el programa, como sockets, descriptores de archivos, conductos, etc., que no se liberan mediante las funciones correspondientes, lo que resulta en una pérdida de recursos del sistema, lo que puede reducir seriamente el rendimiento del sistema y causar ejecución inestable del sistema.

Cómo detectar pérdidas de memoria:

En VS, puede usar la función _CrtDumpMemoryLeaks() proporcionada por el sistema operativo Windows para una detección simple. Esta función solo informa la cantidad aproximada de bytes filtrados y no hay otra información de ubicación más precisa.

int main()
{
 int* p = new int[10];
 // 将该函数放在main函数之后,每次程序退出的时候就会检测是否存在内存泄漏
 _CrtDumpMemoryLeaks();
 return 0;
}
// 程序退出后,在输出窗口中可以检测到泄漏了多少字节,但是没有具体的位置
Detected memory leaks!
Dumping objects ->
{79} normal block at 0x00EC5FB8, 40 bytes long.
Data: <                > CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD
Object dump complete.

Herramienta de detección de fugas de memoria en Linux:

 Cómo evitar pérdidas de memoria:

1. Buenas especificaciones de diseño en la etapa inicial del proyecto, desarrolle buenos estándares de codificación y recuerde liberar el espacio de memoria que se ha solicitado. Pero si encuentra una excepción, incluso si presta atención al lanzamiento, aún puede haber problemas y se requiere una gestión de puntero inteligente para garantizarlo.

2. Use ideas RAII o punteros inteligentes para administrar los recursos.

3. Algunas especificaciones internas de la empresa utilizan bibliotecas de administración de memoria privada implementadas internamente. Esta biblioteca viene con opciones para la detección de fugas de memoria.

4. Si algo sale mal, use la herramienta de pérdida de memoria para detectarlo.

Resumir:

Las fugas de memoria son muy comunes y existen dos soluciones: 1. Tipo de prevención previa, como punteros inteligentes 2. Tipo de detección de errores posterior al evento, como herramientas de detección de fugas

Supongo que te gusta

Origin blog.csdn.net/qq_66767938/article/details/130311962
Recomendado
Clasificación