In-depth understanding of C&C++ memory management

C&C++ memory management

insert image description here

1. C/C++ memory distribution

Let's take a look at the following code first, and analyze the storage location for each variable:

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. 选择题:
   选项: 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__
   PS:我们要清楚指针变量的大小和指针变量所指向空间的大小的区别,指针变量的大小只和操作系统有关系,32位操作系统默认是4个字节,而64位操作系统默认是8个字节,和这个指针变量是什么数据类型没有任何关系,但是指针所指向空间的大小就和他的数据类型有关。
2. 填空题:
   sizeof(num1) = __40__;  
   sizeof(char2) = __5__;      strlen(char2) = _4___;
   sizeof(pChar3) = __4/8__;     strlen(pChar3) = _4___;
   sizeof(ptr1) = __4/8__;
3. sizeof 和 strlen 区别?
	首先,sizeof是C语言中的一个单目运算符,用来计算数据类型所占空间的大小,单位为字节;而strlen是一个函数,用来计算字符串长度。
    其次,sizeof计算所占空间大小时包括字符串末尾默认添加的/0,比如sizeof(char2),数组中直观看上去存储了4个元素,但是这是字符串末尾会默认加上/0,所以实际上是5个元素,数组每个元素的类型都是char类型的,占一个字节,所以sizeof计算下来数组所占空间的大小是5个字节,然后我们来看看strlen,strlen和sizeof这个单目运算符不同,strlen是一个字符串函数,专门用来计算字符串的长度,遇到/0就停止并且/0不会计入字符串的长度,所以strlen(char2)的大小是4.

insert image description here

【illustrate】

  1. The stack is also called the stack – non-static local variables/function parameters/return values, etc., the stack grows downward.

  2. Memory-mapped segments are efficient I/O mappings for loading a shared dynamic memory bank. Users can use the system interface to create shared shared memory for inter-process communication.

  3. The heap is used for dynamic memory allocation when the program is running, and the heap can grow upwards.

  4. Data segment – ​​stores global and static data.

  5. Code Segment – ​​executable code/read-only constants

2. Dynamic memory management in C language: malloc/realloc/calloc/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 );
}

2.1 The difference between malloc/callic/realloc

(1) The prototype of the malloc function
  is void *malloc(unsigned int num_bytes); num_byte is the size of the space to be applied for, which needs to be calculated manually, such as int *p = (int ) malloc(20 sizeof(int)), if compiled If the default int is stored in 4 bytes, then the calculation result is 80Byte, apply for a continuous space of 80Byte at a time, and force-convert the base address of the space to int type, and assign it to the pointer p. At this time, the requested memory value is uncertain .


  (2) The prototype of the calloc function is void *calloc(size_t n, size_t size); it has one more parameter than the malloc function, and does not need to artificially calculate the size of the space. For example, if he wants to apply for 20 int type spaces, he will int * p = (int *)calloc(20, sizeof(int)), which saves the trouble of artificial space calculation. But this is not the most important difference between them. The value of the space after the malloc application is random and not initialized, but the calloc initializes the space one by one after the application and sets the value to 0;

  Then there is a problem here, since calloc opens up space and initializes the space to 0 by default, why do we still need to learn malloc?

​ In fact, for programming, many functions have two sides. Since the calloc function needs to initialize values ​​for each space, it must be less efficient than malloc. In the real world, space applications in many cases do not require initial values. That is why many beginners are more exposed to the malloc function.

2.2 The realization principle of malloc

3. C++ memory management method

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

Dynamic memory management is performed through the new operator and the delete operator .

3.1 new/delete operation built-in type

#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <vector>
using namespace std;

int main()
{
    
    
	//int* p1 = new int;  // 不会初始化,申请一个int
	int* p1 = new int(10); // 申请一个int,初始化10
	int* p3 = new int[10]; // 申请10个int的数组
	int* p4 = new int[10] {
    
    1, 2, 3, 4};
//切记 new要和delete配对使用,如果new和free一起使用结果是不确定的。
	delete p1;
    delete p3;
    delete[] p4;
	return 0;
}

insert image description here

Note: To apply for and release the space of a single element, use the new and delete operators, to apply for and release continuous space, use new[] and delete[], note: use them together.

3.2 new and delete operation custom type

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

Note: When applying for a custom type of space, new will call the constructor, delete will call the destructor, but malloc and free will not.

Let's take a look at the following code:

class Stack
{
    
    
public:
	Stack()
	{
    
    
		cout << "Stack()" << endl;
		_a = new int[4];
		_top = 0;
		_capacity = 4;
	}

	~Stack()
	{
    
    
		cout << "~Stack()" << endl;
		delete[] _a;
		_top = _capacity = 0;
	}

private:
	int* _a;
	int _top;
	int _capacity;
};
int main()
{
    
    
    Stack st;
    Stack* pst=new Stack;
    delete pst;
    return o;
}

insert image description here

  We can clearly see from the above figure that we need to call the destructor first to clear all the data in the space opened up in the heap, and then release pst through the delete operator.

  So he has two steps, the first step is to call the destructor, and the second step is to use the delete operator to release, so free cannot be used in this case, because the destructor will not be called, which will cause a memory leak.

Let's look at a special case at the end: use both free and delete to report an error, and you can only use delete [ ] to release

insert image description here

4. Operator new and operator delete functions (explain important points)

4.1 operator new and operator delete functions (emphasis)

  new and delete are operators for users to apply and release dynamic memory. operator new and operator delete are global functions provided by the system. new calls the operator new global function at the bottom layer to apply for space, and delete uses the operator delete global function at the bottom layer to release space .

/*
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)

Through the implementation of the above two global functions, we know:

operator new actually applies for space through malloc. If malloc successfully applies for space, it will return directly. Otherwise, it will execute the countermeasures provided by the user for insufficient space. If the user provides this measure, it will continue to apply, otherwise it will throw an exception. operator delete finally releases space through free.

Let's take a look at the following easy-to-understand code:

int main()
{
    
    
	 //失败了抛异常
	int* p1 = (int*)operator new(sizeof(int*));

	// 失败返回nullptr
	int* p2 = (int*)malloc(sizeof(int*));
	if (p2 == nullptr)
	{
    
    
		perror("malloc fail");
	}

	 //申请空间 operator new -> 封装malloc
	 //调用构造函数
	A* p5 = new A;

	 //先调用析构函数
	 //再operator delete p5指向的空间
	 operator delete -> free
	 delete p5;

	 //申请空间 operator new[] ->operator new-> 封装malloc
	 调用10次构造函数
	 A* p6 = new A[10];
	
	 //先调用10次析构函数
	 //再operator delete[] p6指向的空间
	 delete[] p6;


	int* p7 = new int[10];
	free(p7);  // 正常释放

	A* p8 = new A;
	free(p8); // 少调用的析构函数
	delete p8;

	Stack st;

	Stack* pst = new Stack;
	delete pst;
	free(pst); // 少调用了析构函数 -- 有内存泄漏

	 //结论:new/malloc系列 有底层实现机制有关联交叉。不匹配使用可能有问题,可能没问题,建议大家一定匹配使用

	A* p9 = new A[10];
	free(p9);
	delete p9;
	delete[] p9;

	return 0;
}

Summarize:

  The new operation is implemented by calling operator new (the operator here is not an operator overload). When it comes to the essence, operator new is to use C++ encapsulation to encapsulate malloc, so the bottom layer of new is implemented using malloc, but new will continue after calling operator Call the constructor to initialize, and delete first calls the destructor and then calls operator delete.

  The difference between operator new[] and operator new lies in the content inside the square brackets. This content is the number of objects. Operator new opens up space first, and then only calls the constructor once, but if it is operator[x], then open up space Then call the constructor x times, operator delete and operator delete[] are the same.

5. Implementation principle of new and delete

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

5.2 Custom types

insert image description here

6. Position new expression (placement-new) (understand)

  Positioning the new expression is to call the constructor to initialize an object in the allocated original memory space.
Usage 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 Usage
scenario:
  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.

class A
{
    
    
public:
	A(int a = 0)//A的构造函数,里面有一个缺省参数
		: _a(a)	//初始化列表
	{
    
    
		cout << "A():" << this << endl;
	}
	~A()//析构函数
	{
    
    
		cout << "~A():" << this << endl;
	}
private:
	int _a;
};


int main()
{
    
    
	A aa;

	A* p1 = (A*)malloc(sizeof(A));//malloc只申请开辟内存空间,不对空间内容初始化,所以这里的p1指针指向的空间并没有初始化
    		// p1现在指向的只不过是与A对象相同大小的一段空间,还不能算是一个对象,因为构造函数没有执行
	if (p1 == nullptr)
	{
    
    
		perror("malloc fail");
	}
    //但是如果我们想对一块已经开辟好(存在)的空间进行初始化,就得用到定位new;
	// 对一块已有的空间初始化 -- 定位new
	//new(p1)A;		// 注意:如果A类的构造函数有参数时,此处需要传参
	new(p1)A(1);

	A* p2 = new A;

	p1->~A();
	free(p1);

	delete p2;
	
    A* p3 = (A*)operator new(sizeof(A));
	new(p3)A(10);
	p2->~A();
	operator delete(p3);
	return 0;
}

	return 0;
}

ps: Some people may ask that since the malloc function can also use the positioning new to initialize the space opened up after the malloc function opens up the space, can the positioning new operation be performed after the new function opens up the space?

   The answer is absolutely not, because we must first make it clear that the initialization can only be performed once, and the new operator will automatically call the constructor to initialize the opened space after opening the space, so it is not possible to use positioning new to initialize again. Here is a review of the previous knowledge. If the constructor of the class written by the programmer is displayed by the programmer, the constructor written by himself will be used. If there is no self-definition, then the default constructor of the class automatically generated by the compiler will be called.

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

8. Memory pool

8.1 Why use memory pool

  The default memory management (new, delete, malloc, free) of C++ programs will frequently allocate and release memory on the heap, resulting in performance loss, generating a large number of memory fragments , and reducing memory utilization. The default memory management is designed to be more general, so it cannot achieve the ultimate performance.

  Therefore, it is often necessary to design a dedicated memory manager based on business needs to facilitate memory management for specific data structures and usage scenarios, such as memory pools.

8.2 Principle of memory pool

The idea of ​​the memory pool is to pre-apply to allocate a certain number of memory blocks with a preset size for backup   before actually using the memory . When there is a new memory demand, a part of the memory block is allocated from the memory pool. If the memory block is not enough, continue to apply for new memory. When the memory is released, it returns to the memory block for subsequent reuse, making memory usage more efficient. It is improved, and generally will not produce uncontrollable memory fragmentation.

insert image description here

Summarize

  This blog involves the memory management of C&C++, a review of the content of dynamic memory development in C language such as malloc and calloc, and an in-depth analysis of the new and delete operators in C++. It briefly talks about the relationship between the memory pool and the heap. Everyone helps~

Guess you like

Origin blog.csdn.net/MDLYB/article/details/130114346