【C ++】 —— new and delete

1. The basic situation of new and delete

Speaking of new and delete, malloc and free, everyone is already familiar with it. I also mentioned related knowledge points in a previous blog post. For details, please see malloc and new

1. The difference between malloc and new

There are three main points about the difference between malloc and new

  1. malloc opens up memory by bytes ; when new opens up memory, it needs to specify the type new int [40], so malloc open up memory returns void *, operator new-> int *

  2. .malloc is only responsible for opening up space . New not only has malloc function, but also can initialize data.
    There are two ways for new to open up space. For example: new int (20);-> give the initial value new int 20 ;-> specify the number of elements and initialize to 0

  3. malloc fails to open memory and returns a nullptr pointer ; new throws an exception of type bad_alloc

The following code is the method to judge the failure of memory development:

try
	{
		int* p = new int;
		delete p;
	}
	catch (const bad_alloc & err)
	{
		cerr << err.what() << endl;
	}

Remember, you cannot use if (p == nullptr)

2. The difference between malloc and new, free and delete

(1) The difference between free and delete
delete: call the destructor, and then free. But delete (int *) p is only memory free for shaping,
because the underlying implementation of delete is as follows:

void operator delete(void* ptr)
{
	cout << "operator delete addr:" << ptr << endl;
	free(ptr);
}

If delete p; (p is a pointer to an object), call p to destructor of the object, and then call operator delete to free the memory space.
But if p is an integer, there is no external heap space, no destructor is needed, and delete and free have the same effect

(2) The difference between malloc and new The difference between
them is the same as free and delete.
The underlying implementation of new is as follows:
first call operator new to open up memory space, and then call the object's constructor (initialization)

void* operator new(size_t size)
{
	void* p = malloc(size);
	if (p == nullptr)
		throw bad_alloc();
	cout << "operator new addr:" << p << endl;
	return p;
}

3. Can new and delete be mixed? Why does c ++ distinguish between memory allocation and deallocation of single elements and arrays?

(1) Conclusion In
general, new delete and new [] delete [] are required for
special use :

  1. For ordinary compiler built-in types can be mixed
  2. Custom class type , not mix destructor, in order to invoke the correct destructor, then the array of objects when opened will be more open four bytes, the number of recording object

(2) Principle
First, let's take a look at the underlying implementation of array new and delete as follows:

void* operator new[](size_t size)
{
	void* p = malloc(size);
	if (p == nullptr)
		throw bad_alloc();
	cout << "operator new[] addr:" << p << endl;
	return p;
}

void operator delete[](void* ptr)
{
	cout << "operator delete[] addr:" << ptr << endl;
	free(ptr);
}

Furthermore, let's write a Test object using

class Test
{
public:
	Test(int data = 10)  { cout << "Test()" << endl; }
	~Test() {  cout << "~Test()" << endl; }
private:
	int ma;
};

When we use it in the main function like this

int main()
{
    Test* p2 = new Test[5];
	cout << "p2:" << p2 << endl;
	delete[] p2;
	return 0;
}

No problem at all, the output is as follows:
Insert picture description here
However, when we use it in the main function like this

Test* p2 = new Test[5];
	cout << "p2:" << p2 << endl;
	delete p2;

But there is a problem:
Insert picture description here
what is the reason?
Let's first analyze this code. Because delete calls the destructor, the destructor has this pointer to know the address of the destructor, but it doesn't know how many objects to destruct, so when opening up memory, 4 more bytes are opened The number of stored objects is shown in the following figure:
Insert picture description here
So, we opened up four more bytes during development, but from the above error results, four bytes are lost when returning, so an error is reported.

Second, the object pool

Speaking of object pools, let's first write a chain queue implementation:

#include<iostream>
using namespace std;

template<typename T>
class Queue
{
public:
	Queue()
	{
		_front = _rear = new QueueTtem();
	}
	~Queue()
	{
		QueueTtem* cur = _front;
		while (cur != nullptr)
		{
			_front = _front->_next;
			delete cur;
			cur = _front;
		}
	}
	void push(const T& val)//入队操作
	{
		QueueTtem *item = new QueueTtem(val);
		_rear->_next = item;
		_rear = item;
	}
	void pop()
	{
		if (empty())
			return;
		QueueTtem* first = _front->_next;
		_front->_next = first->_next;
		if (_front->_next == nullptr)//只有一个结点的时候
		{
			_rear = _front;
		}
		delete first;
	}
	T front() const
	{
		return  _front->_next->data;
	}
	bool empty()const { return _front == _rear; }
private:
	struct QueueTtem
	{
		QueueTtem(T data = T()):_data(data),_next(nullptr){ }
		T _data;
		QueueTtem* _next;
	};

	QueueTtem* _front;//指向头结点
	QueueTtem* _rear;//指向队尾

};
int main()
{
	Queue<int> que;
	for (int i = 0; i < 1000000; ++i)
	{
		que.push(i);
		que.pop();
	}
	cout << que.empty() << endl;
	return 0;
}

In the main function, we implement the dequeue and enqueue operations for 1000000 data as follows:

int main()
{
	Queue<int> que;
	for (int i = 0; i < 1000000; ++i)
	{
		que.push(i);// QueueTtem(i)
		que.pop();//QueueTtem
	}
	cout << que.empty() << endl;
	return 0;
}

But thinking about the above code carefully seems a bit inappropriate. Because we
call malloc and free a lot in a short time, it has an impact on program performance. So how to solve this problem?
We have introduced the concept of object pooling. Its main function is to provide custom memory management for QueueTtem.
There are data fields and pointer fields, linking all the nodes together in a chain, the effect is as follows:
Insert picture description here
The following is the specific implementation
(1) First define a pool entry node outside the class

template<typename T>
typename Queue<T>::QueueTtem* Queue<T>::QueueTtem::_itemPool = nullptr;

Note: If there is no typename, the compilation will be wrong, because at this step, it has not been initialized, I do n’t know what it is, whether it is treated as a pointer or multiplication is unclear,
so the role of typename is to tell the compiler to nest later The name of the class is a type
(2) implementation of the new operator

void* operator new(size_t size)
		{
			if (_itemPool == nullptr)
			{
				_itemPool = (QueueTtem*)new char[POOL_ITEM_SIZE * sizeof(QueueTtem)];//按字节开辟
				QueueTtem* p = _itemPool;
				for (; p < _itemPool + POOL_ITEM_SIZE-1; ++p)
				{
					p->_next = p + 1;
				}
				p->_next = nullptr;//最后一个结点的地址域
			}
			//表示池里面是有结点的,则找到入口结点即可
			QueueTtem* p = _itemPool;
			_itemPool = _itemPool->_next;
		}

(2) Implementation of the delete operator

void* operator delete(size_t size)
		{
			QueueTtem* p = (QueueTtem*)ptr;
			//归还过程就是把它加到第一个结点后面即可
			p->_next = _itemPool;
		}

Suppose that the state of the memory pool is as follows. The p + 1 node now wants to return the node.
Insert picture description here
Insert picture description here
Add the _next field of the node that you want to return to the node pointed to by _itemPool, and then change the point of _itemPool to let it point to the Just return the node.

After the memory pool has passed, the implementation of the above chain queue is as follows:

#include<iostream>
using namespace std;

template<typename T>
class Queue
{
public:
	Queue()
	{
		_front = _rear = new QueueTtem();
	}
	~Queue()
	{
		QueueTtem* cur = _front;
		while (cur != nullptr)
		{
			_front = _front->_next;
			delete cur;
			cur = _front;
		}
	}
	void push(const T& val)//入队操作
	{
		QueueTtem *item = new QueueTtem(val);
		_rear->_next = item;
		_rear = item;
	}
	void pop()
	{
		if (empty())
			return;
		QueueTtem* first = _front->_next;
		_front->_next = first->_next;
		if (_front->_next == nullptr)//只有一个结点的时候
		{
			_rear = _front;
		}
		delete first;
	}
	T front() const
	{
		return  _front->_next->data;
	}
	bool empty()const { return _front == _rear; }
private:
	struct QueueTtem//产生一个QueueTtem的对象池(10000个QueueTtem节点)
	{
		QueueTtem(T data = T()):_data(data),_next(nullptr){ }
		//对QueueTtem提供自定义的内存管理,这两个函数都是静态的方法因为使用的时候都还没有对象
		void* operator new(size_t size)
		{
			if (_itemPool == nullptr)
			{
				_itemPool = (QueueTtem*)new char[POOL_ITEM_SIZE * sizeof(QueueTtem)];//按字节开辟
				QueueTtem* p = _itemPool;
				for (; p < _itemPool + POOL_ITEM_SIZE-1; ++p)
				{
					p->_next = p + 1;
				}
				p->_next = nullptr;//最后一个结点的地址域
			}
			//表示池里面是有结点的,则找到入口结点即可
			QueueTtem* p = _itemPool;
			_itemPool = _itemPool->_next;
		}
		void* operator delete(size_t size)
		{
			QueueTtem* p = (QueueTtem*)ptr;
			//归还过程就是把它加到第一个结点后面即可
			p->_next = _itemPool;
		}
		T _data;
		QueueTtem* _next;
		static QueueTtem* _itemPool;
		static const int POOL_ITEM_SIZE = 1000000;

	};

	QueueTtem* _front;//指向头结点
	QueueTtem* _rear;//指向队尾

};
template<typename T>
typename Queue<T>::QueueTtem* Queue<T>::QueueTtem::_itemPool = nullptr;
int main()
{
	Queue<int> que;
	//短时间内大量调用malloc和free,对程序性能有所影响
	for (int i = 0; i < 1000000; ++i)
	{
		que.push(i);// QueueTtem(i)
		que.pop();//QueueTtem
	}
	cout << que.empty() << endl;
	return 0;
}
Published 98 original articles · won praise 9 · views 3666

Guess you like

Origin blog.csdn.net/qq_43412060/article/details/105156837