[Hou Jie C + +] (memory management)

Posted by peterjoel on Mon, 24 Jan 2022 14:58:30 +0100

Lecture 1: primitives

Four kinds of memory allocation and release


During programming, the memory can be operated directly or indirectly through the above methods. The following describes four C + + memory operation methods:

1.::operator new() calls malloc,::operator delete() calls free

2. malloc and new can usually be used to allocate memory. Of course,:: operator new() (a global function) and allocator allocator can also be used to operate memory. The usage of these functions will be described in detail below. For different compilers, the interface of allocate function is also different:

For GNU C, different versions are different:

In this picture__ gnu_cxx::__pool_alloc().allocate() corresponds to allocator () in the previous figure allocate().

It is very common to allocate memory through malloc and new and release memory through free and delete. It is rare to operate memory through:: operator new. Allocator allocator allocator allocator operation memory is widely used in STL source code, and it is used differently for different compilation environments. The following example is based on the VS2019 environment

#include <iostream>
#include <complex>
#include <memory>//std::allocator

using namespace std;
namespace jj01
{
	void test_primitives1()
	{
		cout << "\ntest_primitives().......... \n";
		void* p1 = malloc(512);//512 bytes
		free(p1);

		complex<int>* p2 = new complex<int>;//one object a unit
		delete p2;

		void* p3 = ::operator new(512);//512 bytes
		::operator delete(p3);

		//The following uses allocators provided by the C + + standard library.
		//Although the interface has standard specifications, the implementation manufacturer does not fully comply with them; The following three forms are slightly different.
#ifdef _MSC_VER
		//VC interface
		//The following two functions are non static and must be called through object. Three ints are assigned below
		//Allocator < int > () temporary object
		int* p4 = allocator<int>().allocate(3, (int*)0);
		p4[0] = 666;
		p4[1] = 999;
		p4[2] = 888;
		cout << "p4[0] = " << p4[0] << endl;
		cout << "p4[1] = " << p4[1] << endl;
		cout << "p4[2] = " << p4[2] << endl;
		//When returning the memory, the original allocated memory size shows that this kind of thing can only be done by the container
		allocator<int>().deallocate(p4, 3);
#endif // _MSC_VER

	}
}

int main(void)
{
	jj01::test_primitives1();
	return 0;
}
test_primitives()..........
p4[0] = 666
p4[1] = 999
p4[2] = 888

Visible int * P4 = allocator() Allocate (3, (int *) 0) operation successfully applied for space of three ints.

new/delete expression of basic components

1. Memory request


The above figure reveals what the compiler does behind the new operation:

1. The first step is to allocate the memory size of a target type through the operator new() operation, here is the size of the Complex;
2. Step 2: pass static_cast casts the obtained memory block into a pointer of the target type. Here is Complex*
3. The third version calls the constructor of the target type, but it should be noted that only the compiler can call the constructor directly through methods such as PC - > complex:: complex (1, 2), which will cause errors.

It is worth noting that inside the operator new() operation, the malloc() function is called.

2. Memory release


Similarly, the first step of the delete operation is to call the destructor of the object, and then release the memory through the operator delete() function. In essence, it also calls the free function.

3. The simulation compiler calls constructors and destructors directly

#include <iostream>
#include <string>
#include <memory>//std::allocator

using namespace std;
namespace jj02
{
	class A
	{
	public:
		int id;

		A():id(0) { cout << "default ctor. this=" << this << " id=" << id << endl; }
		A(int i):id(i) { cout << "ctor. this=" << this << " id=" << id << endl; }
		~A() { cout << "dtor. this=" << this << " id=" << id << endl; }
	};

	void test_call_all_ctor_directly()
	{
		cout << "\ntest_call_ctor_directly().......... \n";
		string* pstr = new string;
		cout << "str= " << *pstr << endl;
		//! pstr->string::string("jjhou");  
		//[Error] 'class std::basic_string<char>' has no member named 'string'
		//!  pstr->~string(); 	// crash -- its language and French meaning are correct. crash is only marked because of the previous line  
		cout << "str= " << *pstr << endl;

		A* pA = new A(1);
		cout << pA->id << endl;   	//1
		pA->A::A(3);
		cout << pA->id << endl;
		//!	pA->A::A(3);                //in VC6 : ctor. this=000307A8 id=3
		//in GCC : [Error] cannot call constructor 'jj02::A::A' directly

		A::A(5);
		//!	A::A(5);	  				//in VC6 : ctor. this=0013FF60 id=5
		//         dtor. this=0013FF60  	
		//in GCC : [Error] cannot call constructor 'jj02::A::A' directly
		//         [Note] for a function-style cast, remove the redundant '::A'

		cout << pA->id << endl;   	//in VC6 : 3
		//in GCC : 1  	

		delete pA;

		//simulate new
		void* p = ::operator new(sizeof(A));
		cout << "p=" << p << endl; 	//p=000307A8
		pA = static_cast<A*>(p);
		pA->A::A(2);
		//!	pA->A::A(2);				//in VC6 : ctor. this=000307A8 id=2
		//in GCC : [Error] cannot call constructor 'jj02::A::A' directly  	

		cout << pA->id << endl;     //in VC6 : 2
		//in GCC : 0  	

		//simulate delete
		pA->~A();//Preconstruction
		::operator delete(pA);//free() to free memory
	}
}

int main(void)
{
	jj02::test_call_all_ctor_directly();
	return 0;
}
test_call_ctor_directly()..........
str=
str=
ctor. this=0149D1C8 id=1
1
ctor. this=0149D1C8 id=3
3
ctor. this=0118FB48 id=5
dtor. this=0118FB48 id=5
3
dtor. this=0149D1C8 id=3
p=0149FED0
ctor. this=0149FED0 id=2
2
dtor. this=0149FED0 id=2

Array new

Lesson 2: malloc/free

Lesson 3: std::allocator

Lesson 4: other allocators

Lesson 5: loki::allocator

Topics: C++ Back-end