[C++] First knowledge of C&C++ memory management

foreword

We all know that C&C++ is a very performance-oriented language, so the memory management of C&C++ is something that every C/C++ learner must master. In this chapter, we do not explain C&C++ memory management in depth, but introduce the basics of C&C++ memory management. , to pave the way for our in-depth understanding of C&C++ memory management in the future


1. C/C++ memory distribution

1 Introduction

Several areas of C/C++ program memory allocation:
  1. Stack area (stack): When a function is executed, the storage units of local variables in the function can be created on the stack, and these storage units are automatically released when the function execution ends. The stack memory allocation operation is built into the instruction set of the processor, which is very efficient, but the allocated memory capacity is limited. The stack area mainly stores local variables allocated by running functions, function parameters, return data, return address, etc. The stack grows downward.
  2. Memory-mapped segment : It is an efficient I/O mapping method for loading a shared dynamic memory bank. Users can use the system interface to create shared shared memory for inter-process communication.
  3. Heap area (heap): Used for dynamic memory allocation when the program is running . Generally, it is allocated and released by the programmer. If the programmer does not release it, it may be reclaimed by the OS when the program ends. The allocation method is similar to a linked list. The heap grows upwards
  4. The data segment (static area) stores global variables and static data. Released by the system after the program ends.
  5. Code segment (constant area): store the binary code of the function body (class member functions and global functions) and some read-only constants.

2. Practice

After reading the above introduction, let's do some exercises to consolidate our knowledge

int globalVar = 1;
static int staticGlobalVar = 1;
void Test()
{
    
    
	static int staticVar = 1;
	int localVar = 1;
	int num1[10] = {
    
     1, 2, 3, 4 };
	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. Multiple choice question:
    Options: A. Stack B. Heap C. Data segment (static area) D. Code segment (constant area)
    Where is globalVar? ____ Where is staticGlobalVar? ____
    Where is staticVar? ____ Where is localVar located? ____
    Where is num1? ____

    Where is char2? Where is ____ *char2? ___
    Where is pChar3? ____ Where is *pChar3? ____
    Where is ptr1? ____ Where is *ptr1? ____

The correct answer is: CCCAA AAADAB

I believe that the first five are not difficult for those who have learned C language memory management, but the latter six will be difficult. Let's see them one by one!

Question 1: Because globalVar is a global variable , global variables are allocated to the data segment (static area) according to the C/C++ memory distribution.
Question 2: Because staticGlobalVar is a global variable and is staticmodified by keywords, it should be allocated to the data segment (static area) according to the C/C++ memory distribution.
Question 3: Because staticVar is modified by keywords, variables modified staticaccording to C/C++ memory distribution are allocated to the data segment (static area). Question 4: Because localVar is a local variable, local variables should be allocated on the stack according to the C/C++ memory distribution. Question 5: Although num1 is an array name, it is still a local variable. According to the C/C++ memory distribution, local variables should be allocated on the stack.static

Question 6: Although char2 is an array name, it is still a local variable. According to the C/C++ memory distribution, local variables should be allocated on the stack.
Question 7: char2 is the name of the array, and the name of the array is the address of the first element. *char means that it is actually a, but the space opened up by the array is still on the stack. *char is still a local variable, and local variables are distributed according to C/C++ memory Should be allocated on the stack.
Note: The array here is actually a copy of the constant string on the stack

Question 8: pChar3 represents a pointer. This pointer variable is a local variable. According to the C/C++ memory distribution, local variables should be allocated on the stack.
Question 9: *pChar3 represents the content in the pointer, which is a constant string. According to the C/C++ memory distribution, read-only constants should be allocated to the code segment (constant area).

Question 10: ptr1 represents a pointer. This pointer variable is a local variable. According to the C/C++ memory distribution, local variables should be allocated on the stack.
Question 11: *ptr1 represents the content in the pointer. Since the space used by this content comes from malloc, dynamic memory allocation should be allocated to the heap according to the C/C++ memory distribution.

Only by mastering this question can we have a better allocation management of C&C++ memory

After reading the above question, let’s continue to look at the following question (the code is the same as above)
2. Fill in the blank question:
  sizeof(num1) = ____;
  sizeof(char2) = ____; strlen(char2) = ____;
  sizeof(pChar3) = ____; strlen(pChar3) = ____;
  sizeof(ptr1) = ____;

The answer is: 40 5 4 4/8 4 4/8
Question 1: The number of arrays intis 10, total 4 * 10 = 40
The second question: The number charof arrays is 5 (don’t forget '\0'), 1 * 5 = 5
the total Question 3: strlenThe number of counting characters is 4 ( strlendo not count '\0'), totaling Question 4
4 : pChar3 is a pointer, pointers have different sizes on different platforms, totaling 4 / 8
Question 5: strlenCounting the number of characters The number is 4 ( strlen'\0' is not counted), totaling Question 4
6 : ptr1 is a pointer, and pointers have different sizes on different platforms, totaling4 / 8

After mastering these two questions, we can further explore the memory management of C&C++.

2. Dynamic memory management in C language: 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);
	// 2.这里需要free(p2)吗?
	free(p3);
}

Question 1:

All three allocate memory, and they are all functions in the C language <stdlib.h> library, but there are some differences.
(1.) malloc function, its prototype

void* malloc(unsigned int num_bytes);

The num_byte here is the size of the space to be applied, which needs to be calculated manually. The value of the space after malloc application is random and not initialized.
(2.) calloc function, its prototype

void* calloc(size_t n, size_t size);

There is one more parameter than the malloc function, and there is no need to artificially calculate the size of the space. The first parameter is the number to be applied, and the second parameter is the size of each type of bytes. After calloc is applied, initialize the space one by one, and set the value to 0; since the calloc function needs to initialize a value for each space, its efficiency is lower than that of malloc.
(3.) realloc function, its prototype

void* realloc(void *ptr, size_t new_Size)

It is used to readjust (expand or shrink) the dynamic memory. If ptr does not point to a dynamically allocated space, the reallocbehavior is equivalent to once malloc. ptr is a pointer to the original space address, and new_size is the total size that needs to be expanded next. After the function call is successful, the original space will be released automatically.

Question 2:

No, reallocthe originally requested space will be released automatically after the function call succeeds.

3. C++ memory management method

The C language memory management method can continue to be used in C++, but it is helpless in some places, and it is more troublesome to use, so C++ has proposed its own memory management method: dynamic memory management through and newoperators delete.

1. new/delete operation built-in type

example code

//new / delete操作内置类型
#include<iostream>
using namespace std;
void Test()
{
    
    
	// 动态申请一个int类型的空间
	int* ptr1 = new int;
	// 动态申请一个int类型的空间并初始化为10
	int* ptr2 = new int(10);
	// 动态申请10个int类型的空间
	int* ptr3 = new int[10];
	delete ptr1;
	delete ptr2;
	//对多个的申请的释放要用 []
	delete[] ptr3;
}
int main()
{
    
    
	Test();
	return 0;
}

Result demonstration:
insert image description here
Note: apply and release space for a single element, use newand deleteoperator, apply and release continuous space, use new[]anddelete[]

Note: Matching and using, the unnecessary mallocspace is deletereleased, newand the free space freeis used to release, which may cause an error.

2. New and delete operate custom types

  When applying for a custom type of space, newthe constructor will be called, and deletethe destructor will be called, but mallocnot free. deleteWhen releasing space, call the destructor of the custom type first and then release the requested space

//new和delete操作自定义类型
#include<iostream>
using namespace std;
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;
}

insert image description here

3. Operator new and operator delete functions

Note: These two functions are not operator overloading, they are just named a bit strangely...

new and delete are operators for users to apply and release dynamic memory , operator newand operator deleteare global functions provided by the system . new calls operator newglobal functions at the bottom layer to apply for space, and delete uses operator deleteglobal functions at the bottom layer to release space. (new and delete are actually a kind of encapsulation
for our users )


operator new: This function actually applies for space through malloc. When malloc successfully applies for space, it returns directly; if space application fails, try to implement countermeasures for insufficient space. If the countermeasures are set by the user, continue to apply, otherwise an exception is thrown .

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

Note: From the source code of the function, we can know that this function cannot call its constructor on an object of a custom type like new


operator delete: This function finally releases space through 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)

Note: From the function source code, we can know that this function cannot call its destructor on objects of custom types like delete


4. Implementation principle of new and delete

a. For built-in types

If you apply for a built-in type of space, new and malloc, delete and free are basically similar, the difference is:
new/delete applies for and releases the space of a single element, new[] and delete[] apply for continuous space, Moreover, new will throw an exception when it fails to apply for space, and malloc will return NULL.

b. For custom types

  • The principle of new
  1. First call the operator new function to apply for space
  2. Then execute the constructor on the requested space to complete the construction of the object
  • The principle of delete
  1. First execute the destructor on the space to complete the cleanup of resources in the object
  2. Then call the operator delete function to release the space of the object
  • The principle of new T[N]
  1. First call the operator new[] function, and actually call the operator new function in operator new[] to complete the application of N object spaces
  2. Then execute the constructor N times on the requested space
  • The principle of delete[]
  1. Execute N times of destructors on the released object space first, and complete the cleanup of resources in N objects
  2. Then call operator delete[] to free up space, and actually call operator delete in operator delete[] to free up space

Let's take a look at the following code

#include<iostream>
using namespace std;
class A
{
    
    
public:
	A(int a = 0)
		:_a(a)
	{
    
    
		cout << "A(): " << this << endl;
	}
	~A()
	{
    
    
		cout << "~A(): " << this << endl;
	}
private:
	int _a;
};
int main()
{
    
    
	//失败后抛异常
	int* ptr1 = (int*)operator new(sizeof(int));
	//失败后返回nullptr
	int* ptr2 = (int*)malloc(sizeof(int));
	if (nullptr == ptr2)
	{
    
    
		perror("malloc fail:");
		exit(-1);
	}

	operator delete(ptr1);
	ptr1 = nullptr;
	free(ptr2);
	ptr2 = nullptr;

	//这里的new调用是       new --> operator new --> malloc
	//调用 1 次构造函数
	A* ptr3 = new A;

	//这里的new调用的是     new --> operator new[] --> operator new --> malloc
	//调用 10 次构造函数
	A* ptr4 = new A[10];

	//调用 1 次析构函数
	//这里delete的调用是    delete --> operator delete --> free 
	delete ptr3;

	//调用 10 次析构函数
	//这里delete[]的调用是  delete[] --> operator delete[] --> opreator delet --> free
	delete[] ptr4;
	return 0;
}

Let's look at the assembly code to understand the underlying principles
Fornew

insert image description here
fornew A[]

insert image description here
fordelete

insert image description here
fordelete[]

insert image description here

5. Talk about the reasons for the mixed use of malloc/free and new/delete to report errors

First of all, we are just talking about the reasons for mixed use and error reporting here. In the actual use process, we still recommend matching them !

  • For the built-in type new, if the dynamic application space does not fail, new is actually just one more initialization than malloc, and the bottom layer of new is also called malloc, so we can of course use the free function to release.
#include<iostream>
using namespace std;
int main()
{
    
    
	//对于内置类型
	int* ptr1 = new int;
	free(ptr1);
	return 0;
}

results in normal operation

insert image description here

  • For the built-in type of delete, because the destructor cannot be called, delete is actually almost the same as free, and the bottom layer of delete also calls free, so we can of course use the delete function to release.
#include<iostream>
using namespace std;
int main()
{
    
    
	//对于内置类型
	int* ptr2 = (int*)malloc(sizeof(int));
	delete ptr2;
	return 0;
}
  • For a custom type of new, the object applied for by new will call its constructor, and we will release it with free and will not call the destructor of the object, which may cause memory leaks (memory leaks will not report an error )

  If resource management is not involved in the class, the program is fine.

#include<iostream>
using namespace std;
class A
{
    
    
public:
	A(int a = 0)
		:_a(a)
	{
    
    
		cout << "A(): " << this << endl;
	}
	~A()
	{
    
    
		cout << "~A(): " << this << endl;
	}
private:
	int _a;
};
int main()
{
    
    
	//对于自定义类型
	A* ptr3 = new A;
	free(ptr3);
	return 0;
}

operation result

insert image description here

  If resource management is involved in the class, the program memory leaks.

#include<iostream>
using namespace std;
class Stack
{
    
    
public:
	Stack(int capacity=4)
		:_capacity(capacity)
		,_size(0)
	{
    
    
		int* tmp = (int*)malloc(sizeof(int) * capacity);
		if (nullptr == tmp)
		{
    
    
			perror("malloc fail:");
		}
		_a = tmp;
		cout << "Stack() :" << this << endl;
	}
	~Stack()
	{
    
    
		free(_a);
		_a = nullptr;
		cout << "~Stack() :" << this << endl;
	}
private:
	int* _a;
	int _size;
	int _capacity;
};
int main()
{
    
    
	//对于自定义类型
	Stack st;
	Stack* ptr3 = new Stack;
	free(ptr3);
	return 0;
}

operation result:

insert image description here

Variable space allocation:
insert image description here
the above code does not call the destructor, causing a memory leak
insert image description here

  • For the delete of a custom type, delete will call the destructor of the class when deleting the space. When we use malloc to apply for space for the custom type, the constructor will not be called, which may cause wrong release when we delete .

  If resource management is not involved in the class, the program is fine.

#include<iostream>
using namespace std;
class A
{
    
    
public:
	A(int a = 0)
		:_a(a)
	{
    
    
		cout << "A(): " << this << endl;
	}
	~A()
	{
    
    
		cout << "~A(): " << this << endl;
	}
private:
	int _a;
};
int main()
{
    
    
	
	A* ptr4 = (A*)malloc(sizeof(A));
	delete ptr4;
	return 0;
}

operation result:

insert image description here

  If resource management is involved in the class, the program crashes (wrong free).

#include<iostream>
using namespace std;
class Stack
{
    
    
public:
	Stack(int capacity=4)
		:_capacity(capacity)
		,_size(0)
	{
    
    
		int* tmp = (int*)malloc(sizeof(int) * capacity);
		if (nullptr == tmp)
		{
    
    
			perror("malloc fail:");
		}
		_a = tmp;
		cout << "Stack() :" << this << endl;
	}
	~Stack()
	{
    
    
		free(_a);
		_a = nullptr;
		cout << "~Stack() :" << this << endl;
	}
private:
	int* _a;
	int _size;
	int _capacity;
};
int main()
{
    
    
	Stack* ptr4 = (Stack*)malloc(sizeof(Stack));
	delete ptr4;
	return 0;
}

Running result: program crashes
insert image description here
insert image description here

  • For the space requested by new[], delete[] should be used to release it, otherwise an error will be reported!
#include<iostream>
using namespace std;
class A
{
    
    
public:
	A(int a = 0)
		:_a(a)
	{
    
    
		cout << "A(): " << this << endl;
	}
	~A()
	{
    
    
		cout << "~A(): " << this << endl;
	}
private:
	int _a;
};
int main()
{
    
    
	A* ptr5 = new A[10];
	//free(ptr5);//报错
	//delete ptr5;//报错
	delete[] ptr5;
	return 0;
}

An error will be reported for mismatched usage!

insert image description here
Then why is this reporting an error?
Because for our compiler, we can know exactly how much space to open up, but how do we know how much space to free A* ptr5 = new A[5];when we use the compiler? delete[] ptr5;(There is no value written in [], which makes the compiler unable to determine)

In order for the compiler to know how much space we need to release for the release of the delete[] type, each compiler handles it differently. In Visual Studio, the actual space opened for new[] is larger than the space written in the code 4 bytes, these 4 bytes are exactly one inttype, used to store the size of the space to be released, and these 4 bytes are opened at the head of the space . Therefore, the address returned to ptr5 is actually offset by 4 bytes from the original address.

For free and delete, they are all released from the current address, causing the release location to be wrong and the program crashes. For delete[], it will be released after certain processing, so the program will not crash!
insert image description here

Four, positioning new expression (placement-new)

Positioning the new expression is to call the constructor to initialize an object in the allocated original memory space .

Format:
new (place_address) type or new (place_address) type(initializer-list)
place_address must be a pointer, and initializer-list is the initialization list of the type

Positioning new expressions are generally used in conjunction with memory pools in practice . Because the memory allocated by the memory pool is not initialized, if it is an object of a custom type, it needs to use the definition expression of new to explicitly call the constructor for initialization.

example code

//定位new表达式(placement - new)
#include<iostream>
using namespace std;
class A
{
    
    
public:
	A(int a = 0)
		: _a(a)
	{
    
    
		cout << "A():" << this << endl;
	}
	~A()
	{
    
    
		cout << "~A():" << this << endl;
	}
private:
	int _a;
};
int main()
{
    
    
	// p1现在指向的只不过是与A对象相同大小的一段空间,还不能算是一个对象,因为构造函数没有执行
	A* p1 = (A*)malloc(sizeof(A));
	new(p1)A; // 注意:如果A类的构造函数有参数时,此处需要传参
	p1->~A(); //析构函数可以显示调用,构造函数一般不可以显示调用,除了使用定位new
	free(p1);
	A* p2 = (A*)operator new(sizeof(A));//operator new 不会调用构造函数
	new(p2)A(10);
	p2->~A();
	operator delete(p2);
	return 0;
}

operation result:

insert image description here

Five, talk about memory leaks

1. What is a memory leak and the harm of a memory leak

  • What is a memory leak: A memory leak refers to a situation in which a program fails to release memory that is no longer in use due to negligence or error. A memory leak does not refer to the physical disappearance of memory, but rather the loss of control over a certain segment of memory due to a design error after the application allocates a certain segment of memory, resulting in a waste of memory.

  • Hazards of memory leaks: Memory leaks occur in long-running programs, which have a great impact, such as operating systems, background services, etc. Memory leaks will lead to slower and slower responses, and eventually freeze.

//内存泄漏
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;
}

2. Classification of memory leaks

In C/C++ programs, we generally care about two aspects of memory leaks:

  • Heap memory leak (Heap leak)
    Heap memory refers to a piece of memory allocated from the heap through malloc / calloc / realloc / new, etc. during program execution as needed, and must be deleted by calling the corresponding free or delete after use. Assuming that the design error of the program causes this part of the memory to not be released, then this part of the space will no longer be used in the future, and Heap Leak will occur.
  • System resource leakage
    refers to the resources allocated by the system used by the program, such as sockets, file descriptors, pipes, etc., which are not released using the corresponding functions, resulting in waste of system resources, which can seriously reduce system performance and cause unstable system execution.

6. The difference between malloc/free and new/delete

What malloc/free and new/delete have in common is that they all apply for space from the heap and need to be released manually by the user. The differences are:

  1. malloc and free are functions, new and delete are operators
  2. The space requested by malloc will not be initialized, but new can be initialized
  3. When malloc applies for space, you need to manually calculate the size of the space and pass it on. New just needs to follow it with the type of space. If there are multiple objects, specify the number of objects in []
  4. The return value of malloc is void*, which must be forced when used, and new does not need it, because new is followed by the type of space
  5. When malloc fails to apply for space, it returns NULL, so it must be judged as empty when using it, new does not need it, but new needs to catch exceptions
  6. When applying for a custom type of object, malloc/free will only open up space, and will not call the constructor and destructor, while new will call the constructor to complete the initialization of the object after applying for space, and delete will call the destructor before releasing the space Complete the cleanup of resources in the space.

Guess you like

Origin blog.csdn.net/qq_65207641/article/details/129133486