Memory management in C + + Basics

C/C + + memory distribution

Program memory in C/C + + can be divided into kernel space, stack, memory mapping segment, heap, data segment and code segment. Among them, we mainly need to understand:

regionfunctionMemory release form
StackStore non static local variables, function parameters and return valuesautomatic
heapProgram dynamic memory developmentManual
Data segmentStore global variables and static variablesautomatic
Code snippetExecutable code, readable constantsautomatic

Example code:

int globalVar = 1; 
static int staticGlobalVar = 1; 
int main()
{
	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);
	return 0;
}

The area corresponding to each variable in the code is shown in the following figure:

Dynamic memory management mode of C language

malloc/calloc/realloc and free are mainly used in C language to manage memory. They can open up space. What's the difference?

  • The difference between malloc and calloc: malloc cannot initialize the opened space, while calloc can initialize the opened space to 0 by byte;
  • The difference between malloc and realloc: malloc allocates a memory block that has not been allocated, while realloc realloc allocates a space that has been allocated. Realloc is divided into in-situ expansion and non local expansion according to the size of the realloc realloc realloc realloc realloc realloc realloc realloc realloc realloc realloc realloc realloc realloc realloc realloc realloc realloc realloc realloc.

Dynamic memory management of C + + language

C + + proposes a way different from C: dynamic memory management through new and delete operators. At the same time, malloc/calloc/realloc are functions, while new/delete is an operator. What's the difference between malloc / free and new/delete?
common ground:

  • Space is requested from the heap and needs to be manually released by the user.

difference:

  • malloc and free are functions, and new and delete are operators
  • The space requested by malloc will not be initialized, but new can be initialized
  • When malloc applies for space, it needs to manually calculate the space size and pass it. new only needs to keep up with the space type
  • The return value of malloc is void *, which must be forcibly converted when used. New is not required, because new is followed by the type of space
  • When malloc fails to apply for space, it returns NULL, so it must be NULL when using it. New does not need it, but new needs to catch exceptions
  • When applying for custom type objects, malloc/free will only open up the space and will not call the constructor and destructor. new will call the constructor to initialize the object after applying for the space, and delete will call the destructor to clean up the resources in the space before releasing the space

operator new and operator delete functions

Operator new and operator delete are global functions provided by the system. New calls the operator new global function at the bottom to apply for space, and delete releases space through the operator delete global function at the bottom.

  • operator new: this function actually applies for space through malloc. When malloc successfully applies for space, it returns directly; If the space application fails, an exception will be thrown. Underlying source code:
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
		// If the memory application fails, bad will be thrown here_ Alloc type exception
		static const std::bad_alloc nomem;
		_RAISE(nomem);
	}
	return (p);
}
  • operator delete: this function finally frees up space through free. Underlying source code:
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;
}

PS: operator new and operator delete encapsulate malloc and free.

Principle of new and delete

1. Built in type
If you apply for a built-in space, new is basically similar to malloc, delete and free. The difference is that new/delete applies for and releases the space of a single element, new [] and delete [] apply for continuous space, and new throws an exception when the application fails, and malloc returns NULL.
2. User defined type
Principle of new:
(1) Call the operator new function to apply for space
(2) Execute the constructor on the applied space to complete the construction of the object
Principle of delete:
(1) Execute the destructor in space to clean up the resources in the object
(2) Call the operator delete function to free up the space of the object
Principle of new T[N]:
(1) Call the operator new [] function, and actually call the operator new function in operator new [] to complete the application of N object spaces
(2) Execute the constructor N times on the requested space
Principle of delete []:
(1) Execute the destructor N times on the released object space to clean up the resources in N objects
(2) call operator delete[] to release the space, and actually invoke operator delete in operator delete[] to release the space.

Built in type of new/delete operation

For built-in types, malloc/free and new/delete have no essential difference, only differences in usage.

void Test()
{
	// Dynamically apply for a space of type int
	int* ptr1 = new int;
	// Dynamically apply for a space of type int and initialize it to 10
	int* ptr2 = new int(10);
	// Dynamically apply for 10 int type spaces
	int* ptr3 = new int[3];
	delete ptr1;
	ptr1 = nullptr;
	delete ptr2;
	ptr2 = nullptr;
	delete[] ptr3;
	ptr3 = nullptr;
}

Note: apply for and release the space of a single element. Use the new and delete operators to apply for and release the continuous space. Use new [] and delete []!!!

int* ptr4 = new int(5);
int* ptr5 = new int[5];

The above two codes are not equal!!! The first sentence initializes the memory space with a value of 5, while the second sentence initializes five memory spaces of type int.
PS: C++11 supports array initialization

int* ptr5 = new int[5]{1,2,3};

Uninitialized spaces default to 0

new/delete operation custom type

class A
{
public:
	A(int a = 10)
		:_a(a)
	{
		cout << "A()->" << this << endl;
	}
	~A()
	{
		cout << "~A()->" << this << endl;
	}
private:
	int _a;
};
int main()
{
	int* p1 = (int*)malloc(sizeof(A));
	int* p2 = (int*)malloc(sizeof(A) * 5);

	A* p3 = new A;
	A* p4 = new A[5];

	free(p1);
	p1 = nullptr;
	free(p2);
	p2 = nullptr;

	delete p3;
	p3 = nullptr;
	delete[] p4;
	p4 = nullptr;
	return 0;
}

During single-step debugging, the monitoring panel and output panel after malloc and new execution are:

The monitoring panel and output panel after the execution of free and delete are:

C + + proposes new and delete to solve two problems:

  1. When applying for custom type space, new will apply for space + call constructor, delete will call destructor + free space, but malloc and free will not!!!
  2. After new fails, it is required to throw exceptions, which is in line with the error mechanism of object-oriented language.

PS: delete and free generally do not fail. If they fail, they are out of bounds in the free space or the position of the free pointer is wrong.

Class specific overload of operator new/delete

For the ListNode of the linked list, the exclusive operator new/ operator delete is overloaded to enable the linked list node to use the memory pool to apply for and release memory and improve efficiency.

class ListNode
{
public:
	ListNode(int val)
		:_next(nullptr)
		, _prev(nullptr)
		, _data(val)
	{}
	void* operator new(size_t n)
	{
		void* p = nullptr;
		// Memory pool - > space configurator in STL
		p = allocator<ListNode>().allocate(1);
		cout << "memory pool allocate" << endl;
		return p;
	}
	void operator delete(void* p)
	{
		allocator<ListNode>().deallocate((ListNode*)p, 1);
		cout << "memory pool deallocate" << endl;
	}
private:
	ListNode* _next;
	ListNode* _prev;
	int _data;
};
class List
{
public:
	List()
	{
		_head = new ListNode(-1);
		_head->_next = _head;
		_head->_prev = _head;
	}
	void PushBack(int val)
	{
		ListNode* newnode = new ListNode(val);
		ListNode* tail = _head->_prev;
		tail->_next = newnode;
		newnode->_prev = tail;
		newnode->_next = _head;
		_head->_prev = newnode;
	}
	~List()
	{
		ListNode* cur = _head->_next;
		while (cur != _head)
		{
			ListNode* next = cur->_next;
			delete cur;
			cur = next;
		}
		delete _head;
		_head = nullptr;
	}
private:
	ListNode* _head;
};
int main()
{
	List L1;
	L1.PushBack(1);
	L1.PushBack(2);
	L1.PushBack(3);
	L1.PushBack(4);
	return 0;
}

Locate new expression

The positioning new expression is to initialize an object in the allocated original memory space by calling the constructor.
Use format:

// place_address must be a pointer
// Initializer list is an initialization list of types
new(place_address) type
new(place_address) type(initialzer-list)

Usage scenario:
In practice, the new positioning expression is generally used in conjunction with the memory pool. Because the memory allocated by the memory pool is not initialized, if it is an object of user-defined type, you need to use the definition expression of new to display and call the constructor to initialize
Use example:

class A
{
public:
	A(int val)
		:_a(val)
	{
		cout << "A()" << endl;
	}
	~A()
	{
		cout << "~A()" << endl;
	}
private:
	int _a;
};
int main()
{
	// Open space
	A* p1 = (A*)malloc(sizeof(A));
	// Locate new initialization
	new(p1) A(3);
	delete p1;
	p1 = nullptr;
	return 0;
}

new = operator new function + positioning new expression

A* p2 = new A(3)
delete p2;
// Equivalent to
A* p3 = (A*)operator new(sizeof(A));
new(p3) A(3); // When a class does not have a default constructor, it must pass parameters
p3->~A(); //Instantiated objects can display call destructors, but cannot display call constructors
operator delete(p3);

Memory leak

Definition of memory leak

A memory leak is a condition in which a program fails to free memory that is no longer in use due to negligence or error.

Harm of memory leakage

Memory leakage of long-running programs has a great impact, such as operating system, background services, etc. memory leakage will lead to slower and slower response and finally get stuck.

void MemoryLeaks()
{
	// 1. The memory has applied for forgetting to release
	int* p1 = (int*)malloc(sizeof(int));
	int* p2 = new int;
	
	// 2. Abnormal safety problems
	int* p3 = new int[10];
	Func(); // Here, the Func function throws an exception, causing delete[] p3 not to be executed and p3 not to be released
	delete[] p3;
}

Classification of memory leaks

  • Heap memory leak
    If a piece of memory allocated from the heap through malloc / calloc / realloc / new is not deleted by calling the corresponding free or delete after it is used up, this part of the space will no longer be used in the future, and Heap Leak will be generated.
  • System resource memory leak
    The program uses the resources allocated by the system, such as sockets, file descriptors, pipes, etc., which are not released by using the corresponding functions, resulting in the waste of system resources, which can seriously reduce the system efficiency and unstable system execution.

Methods to avoid memory leakage

  1. Good design specifications in the early stage of the project, develop good coding specifications, and remember to release the matched memory space.
  2. Use RAII idea or smart pointer to manage resources.
  3. Some company internal specifications use internally implemented private memory management libraries. This set of library comes with memory leak detection function options.
  4. Something's wrong. Use the memory leak tool to detect it.

Failed to apply for space

When malloc and new failed to apply for space, different feedback will appear.

  • malloc failed to apply for space - error code
int main()
{
	// In process oriented language, the way to deal with errors is generally return value + error code
	// Successfully applied for space
	//char* p1 = (char*)malloc(sizeof(char));
	// Failed to apply for space
	char* p1 = (char*)malloc(1024u * 1024u * 1024u * 2u);
	if (p1 == nullptr)
	{
		printf("%d\n", errno);
		perror("malloc fail");
	}
	else
	{
		printf("%p\n", p1);
	}
	return 0;
}

  • new failed to apply for space - throw exception
int main()
{
	// In object-oriented language, the way to deal with errors is to throw exceptions, which is also required in C + +
	try
	{
		//char* p2 = new char[1024u * 1024u * 1024u * 1u];
		//printf("%p\n", p2);
		char* p2 = new char[1024u * 1024u * 1024u * 2u - 1];
		printf("%p\n", p2);
	}
	catch (const exception& e)
	{
		cout << e.what() << endl;
	}
	return 0;
}


PS: how to apply for 4G memory on the heap at one time?

// Compile the program into x64 process and run the following program to try?
#include <iostream>
using namespace std;
int main()
{
	void* p = new char[0xfffffffful];
	cout << "new:" << p << endl;
	cout << sizeof(p) << endl;
	return 0;
}

Keywords: C++ Back-end

Added by hypertech on Sun, 20 Feb 2022 09:13:01 +0200