C/C + + memory management

Posted by Valdoua on Thu, 20 Jan 2022 19:32:50 +0100

C/C + + memory management

C/C + + memory distribution

Let's start with the following code:

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

Do you know where the above variables, pointers, array names, etc. are stored in memory?

If you know, it proves that you are very good. It doesn't matter if you don't know. I'll tell you next.

[description]

  1. Stack is also called stack. It mainly stores non-static local variables / function parameters / return values, etc. the stack grows downward.
  2. Memory mapping segment is a funny I/O mapping method, which is used to load shared dynamic memory libraries. Users can use the system interface to create shared memory for inter process communication.
  3. Heap is used for dynamic memory allocation during program running, and the heap grows upward.
  4. Data segment is also called static area, which is used to store global data and static data.
  5. Code snippets, also known as constant areas, are used to store executable code and read-only constants.

**By the way: * * why does the stack grow downward and the heap grow upward?

In short, when opening up space in the stack area, the high address space is used first. Generally, when opening up space in the heap area, the low address space will be used first.

Next, let's look at the downward growth of the stack through a piece of code

void f2()
{
	int b = 0;
	cout << "b:" << &b << endl;
}

void f1()
{
	int a = 0;
	cout << "a:" << &a << endl;
	f2();
}

int main()
{
    f1();
    return 0;
}

When opening up space in the stack area, the address of the first opened space is higher, so the printed address of a is greater than that of b.

Let's take a look at the heap growing upward through a piece of code

int main()
{
    //Pile up growth
	int* p1 = (int*)malloc(4);
	int* p2 = (int*)malloc(4);
	cout << "p1:"<< p1 << endl;
	cout << "p2:"<< p2 << endl;
    
    return 0;
}

Then why do you say that generally, the low address is used first to open up space in the heap area?

Because you open up space in the heap area, the later space is not necessarily higher than the first space address. If you open up two spaces, then you release the first space and open another space. At this time, the space you open may use the space you released last time. Let's take a look through a piece of code.

int main()
{
    //Under normal circumstances, the later application is larger than the first application, but it is not necessarily
	for (int i = 0; i < 3; i++)
	{
		int* p1 = (int*)malloc(4);
		int* p2 = (int*)malloc(4);
		cout << "p1:"<< p1 << endl;
		cout << "p2:"<< p2 << endl;

		free(p1);
	}

	return 0;
}

Dynamic memory management in C language

malloc/calloc/realloc and free
void Test ()
{ 
    int* p1 = (int*) malloc(sizeof(int)); free(p1); 
    // 1. What is the difference between malloc / calloc / realloc? 
    int* p2 = (int*)calloc(4, sizeof (int));
    int* p3 = (int*)realloc(p2, sizeof(int)*10); 
    // Do you need free(p2) here?
    free(p3 );
}
Interview questions

What is the difference between malloc/calloc/realoc?

In short:

1.malloc dynamically opens space on the heap

2.calloc dynamically opens space on the heap + initializes to 0 Equivalent to malloc+memset

3.realloc aims to expand the existing space

Expansion can be divided into in-situ expansion and offsite expansion

In situ expansion: there is enough space in the back for direct expansion.

Offsite expansion: there is not enough space in the back. Find a new space, copy the contents of the old space to the new space, and then release the old space.

If you want to know more about their use and differences, please go to the blogger's blog: (174 messages) advanced C language - dynamic memory management_ Ustinian% blog - CSDN blog

C + + memory management mode

The memory management mode of C language can continue to be used in C + +, but there are some places where it is powerless and troublesome to use. Therefore, C + + puts forward its own memory management mode: dynamic memory management through new and delete operators.

Built in type of new/delete operation

1. Dynamically apply for a single type of space

int main()
{
    //Apply for a single type of space
    int* p1 = new int;
    
    delete p1;
}

Its function is equivalent to:

int main()
{
    //Dynamically apply for space of a single int type
    int* p2 = (int*)malloc(sizeof(int));
    
    free(p2);
}

2. Dynamically apply for multiple spaces of a certain type

int main()
{
    //Dynamically apply for 10 int type spaces
	int* p3 = new int[10];
    
    delete[] p3;
}

Its function is equivalent to:

int main()
{
    //Dynamically apply for 10 int type spaces
	int* p4 = (int*)malloc(sizeof(int)* 10);
    
    free(p1);
}

3. Dynamically apply for a single type of space and initialize it

int main()
{
    //Dynamically apply for space of single int type and initialize
    int* p5 = new int(3);
    
    free(p5);
}

Its function is equivalent to:

int main()
{
    //Dynamically apply for space of a single int type
    int* p6 = (int*)malloc(sizeof(int));
    *p6 = 3;
    
    free(p6);
}

4. Dynamically apply for and initialize multiple spaces of a certain type

int main()
{
    //Dynamically apply for 4 int type spaces
	int* p7 = new int[4]{0,1,2,3};
    
    delete[] p7;
}

Its function is equivalent to:

int main()
{
     //Dynamically apply for 4 int type spaces
	int* p8 = (int*)malloc(sizeof(int)* 4);
    for(int i = 0;i<4;i++)
    {
        *(p8+i) = i;
    }
    
    free(p8);
}

There is no difference between new/delete and malloc/free for built-in types, but the usage is different.

However, it should be noted that: apply for and release the space of a single element, use the new and delete operators, apply for and release the continuous space, and use new [] and delete []. new/delete new[]/delete [] must match, otherwise an error may occur.

new/delete operation custom type

Let's look at the custom types of new and delete operations through the code

struct ListNode
{
	//struct ListNode* _next;//C
	ListNode* _next;
	ListNode* _prev;
	int _val;

	ListNode(int val = 0)
		:_next(nullptr)
		, _prev(nullptr)
		, _val(val)
	{
		cout << "ListNode(int val = 0)" << endl;
	}

	~ListNode()
	{
		cout << "~ListNode()" << endl;
	}
};

int main()
{
	//C malloc just opens space and free frees space
	struct ListNode* n1 = (struct ListNode*)malloc(sizeof(struct ListNode));
	free(n1);

	//CPP new is for user-defined types, with open space + constructor initialization
	//    delete for user-defined types, the destructor cleans up + frees up space
	ListNode* n2 = new ListNode(5); //Equivalent to BuyListNode(5) in C language
	delete n2;

	struct ListNode* arr3 = (struct ListNode*)malloc(sizeof(struct ListNode)*4);
	free(arr3);

	ListNode* arr4 = new ListNode[4]{1, 2, 3, 4};
	delete[] arr4;
	delete arr4;//Mismatch will crash

	return 0;
}

Through debugging, we can see that malloc only opens up space for custom types, and free frees up space. But new not only opens up space, but also calls the constructor initialization, and delete calls the destructor to clean up and free up space.

summary
  • In C + +, malloc is no different from new if you apply for built-in type objects or arrays
  • If it is a user-defined type, there is a big difference. new and delete are open space + initialization, destruct cleanup + free space, while malloc and free are only open space + free space
  • It is recommended to use new and delete as much as possible in C + + for application release of both built-in and user-defined types

operator new and operator delete functions

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

The global functions operator new and operator delete are used in exactly the same way as malloc and free. Their functions are to apply for and free space on the heap. But the difference is that the processing method is different when it fails. Malloc will return NULL when it fails, and oeperator new will throw an exception after it fails.

struct ListNode
{
	ListNode* _next;
	ListNode* _prev;
	int _val;

	ListNode(int val)
		:_next(nullptr)
		, _prev(nullptr)
		, _val(val)
	{}

};

void f()
{
    ListNode* p1 = (ListNode*)malloc(sizeof(ListNode));
	free(p1);
    //Equivalent to the above two lines of code
	ListNode* p2 = (ListNode*)operator new(sizeof(ListNode));
	operator delete(p2);
}
void test()
{
	//Its usage is exactly the same as malloc and free. Its function is to apply for free space on the heap
	//If it fails, the processing method is different. malloc fails and returns NULL. After operator new fails, an exception is thrown

	void* p3 = malloc(0x7fffffff);
	if (p3 == NULL)
	{
		cout << "malloc fail" << endl;
	}
    
	char* p5 = new char[0x7fffffff];
	cout << "continue" << endl;

 }

int main()
{
	try
	{
		f();
	}
	catch (exception& e)
	{
		cout << e.what() << endl;
	}

	return 0;
}

In fact, the operator new function actually applies for space through malloc. When malloc successfully applies for space, it returns directly; If the application for space fails, try the response measure of insufficient space. If the response measure is set by the user, continue to apply, otherwise throw an exception. At the same time, operator delete finally releases space through free.

Class specific overloading of operator new and operator delete

The following code demonstrates that the ListNode of the linked list node can use the memory pool to apply for and release memory by overloading the exclusive operator new/ operator delete class, so as to improve efficiency.

struct ListNode
{
    ListNode* _next;
    ListNode* _prev;
    int _data;
    
    void* operator new(size_t n)
    {
        void* p = nullptr;
        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;
    }
    
};

class List
{
    public: 
    List()
    {
        _head = new ListNode;
        _head->_next = _head;
        _head->_prev = _head;
    }
    ~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 l;
    
    return 0;
}

Implementation principle of new and delete

Built in type

When applying for a built-in type of space, new and malloc, delete and free are basically similar. The difference is that new/delete applies for and releases a single element space, new [] and delete [] apply for and releases a continuous space, and new throws an exception when the application fails, and malloc returns NULL.

Custom type
  • Principle of new

1. Call the operator new function to apply for space

2. Call the constructor in 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 call operator delete in operator delete[] to release the space.

Positioning new expression (placement new)

The positioning new expression indicates that an object is initialized by calling the constructor in the allocated original memory space.

Use format:

new(place_address)type or new(place_address)type (initializer list)

place_address must be a pointer, and initializer list is an initialization list of type

Usage scenario:

The positioning new expression is generally used with the memory pool in practice. 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 initialize the constructor.

class A
{
public:
	A(int a = 0)
		:_a(a)
	{
		cout << "A()" << this << endl;
	}
	~A()
	{
		cout << "~A()" << endl;
	}
private:
	int _a;
};

int main()
{
	A* p = (A*)malloc(sizeof(A));

	//Equivalent to directly using A* p = new A
	A* p1 = (A*)operator new(sizeof(A));
	//new(p1)A;
    new(p1)A(3); 
    //Locate new, placement new, and explicitly call the constructor
	//The positioning new expression is to initialize an object in the allocated original memory space by calling the constructor.

	p->~A();//Destructors can display calls
    //Equal to delete p
	operator delete(p);
	return 0;
}

be careful:

Before using the positioning new expression, p now points to only A space of the same size as the A object, which can not be regarded as an object because the constructor has not been executed.

Common interview questions

The difference between malloc/free / and new/delete

malloc/free and new/delete have something in common: they both request space from the heap and need to be released manually by the user.

The differences are:

  1. malloc and free are functions, and 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, it needs to manually calculate the space size and pass it. new only needs to follow the space type. If there are multiple objects, specify the number of objects in [].
  4. 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
  5. 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
  6. 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
Memory leak

What is a memory leak, the harm of a memory leak

Memory leak: a memory leak is a condition in which a program fails to release memory that is no longer used due to negligence or error. Memory leakage does not mean the physical disappearance of memory, but the application loses control of a certain section of memory due to design errors, resulting in a waste of memory.

Harm of memory leakage: memory leakage occurs in long-term running programs, which 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 security problem int* p3 = new int[10]; 
    Func(); // Here, the Func function throws an exception, resulting in delete[] p3 not being executed and p3 not being released
    delete[] p3;
}

It should also be emphasized here:

Is the memory leak pointer lost or memory lost?

The answer is that the pointer is lost.

malloc, the essence of new application space is to give you the right to use a piece of memory. The essence of free and delete freeing memory space is to exchange usage rights to the system, so the system can reallocate this memory to others.

If you apply for a piece of space but don't use it but don't release it, it will cause memory leakage. It's a bit like what we say - you don't shit in the manger.

Memory leak classification

In C/C + + programs, we are generally concerned about two aspects of memory leakage:

  • Heap memory leak

Heap memory refers to a piece of memory allocated from the heap through malloc / calloc / realloc / new according to needs during program execution. After it is used up, it must be deleted by calling the corresponding free or delete. Assuming that this part of memory is not released due to the design error of the program, this part of space will no longer be used in the future, and Heap Leak will be generated.

  • System memory leak

It refers to that the program uses the resources allocated by the system, such as sockets, file descriptors, pipes, etc. without using the corresponding functions, resulting in the waste of system resources, which can seriously reduce the system efficiency and unstable system execution.

How to avoid memory leakage
  • Good design specifications in the early stage of the project, develop good coding specifications, and remember to release the matched memory space. ps: this ideal state. However, if there is an exception, even if the attention is released, there may still be a problem. You need the next smart pointer to manage.
  • Use RAII idea or smart pointer to manage resources.
  • Some company internal specifications use internally implemented private memory management libraries. This set of libraries comes with memory leak detection options.
  • Something's wrong. Use the memory leak tool to detect it. ps: but many tools are unreliable or expensive.

Memory leaks are very common. There are two solutions:

1. Preventive in advance. Such as smart pointer, etc.

2. Error checking afterwards. Such as leak detection tools.

How to apply for 4G memory on the heap at one time
int main()
{
	//1byte == 8bit
	//1KB = 1024byte
	//1MB = 1024KB
	//1GB = 1024MB
	//1TB = 1024GB
	//1G
	//void* p1 = malloc(1024 * 1024 * 1024);
	//1.8G
	//void* p1 = malloc(1024 * 1024 * 1024 * 1.8);

	//If you want to malloc 4G space, you have to switch to 64 bits
	//Because you can only apply for 2G space at most under 32-bit, and the continuous space is about 1.8G
	//If 0xffffffff is converted to decimal, it is 4G
	void* p1 = malloc(0xffffffff);
	cout << p1 << endl;

	return 0;
}

Under 32-bit, we can only apply for 2G space at most, and the continuous space is about 1.8G. Therefore, if we want to apply for 4G space, we have to switch to 64 bit platform.

As shown in the following figure:

Topics: C C++