跳至主要內容

剖析new和delete实现原理

张威大约 6 分钟使用指南运算符重载new/delete

剖析new与delete实现原理

[new/delete关键字 | 张威的编程学习笔记 (gitee.io)](https://iszhwei.gitee.io/ccpp/02 c__基础/new、delete.html)

我们先来看一下:malloc与new、delete与free区别

  1. malloc与new的区别:

①malloc按字节开辟内存的;new开辟内存时需要指定类型; ②malloc开辟内存返回的都是void *** ,new相当于运算符重载函数,返回值自动转为指定的类型的指针。** ③malloc只负责开辟内存空间,new不仅仅也有malloc功能,还可以进行数据的初始化。 ④malloc开辟内存失败返回nullptr指针;new抛出的是bad_alloc类型的异常。 ⑤malloc开辟单个元素内存与数组内存是一样的,都是给字节数;new开辟时对单个元素内存后面不需要[],而数组需要[]并给上元素个数

  1. free和delete的区别:

①free不管释放单个元素内存还是数组内存,只需要传入内存的即可。 ②delete释放单个元素内存,不需要加中括号,但释放数据内存时需要加。 ③delete执行其实有两步,先调用析构,再释放;free只有一步

new与delete实现原理进行剖析

int* p = new int;
delete p;

将其转为反汇编open in new window后:

我们发现,也是

new -> operator new
delete -> operator delete

那么我们自己实现一下它:

operator new的实现:

//new:先调用operator开辟内存空间,然后调用对象的构造函数
void* operator new(size_t size)
{
	void *p = malloc(size);
	if (p == nullptr)
	{
		throw bad_alloc();
	}
	cout << "operator new addr:" << p <<endl;
	return p;
}

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

operator delete的实现:

//delete p:调用p指向对象的析构函数,再调用operator delete释放内存空间
void operator delete(void *ptr)
{
	cout << "operator delete addr:" << ptr <<endl;
	free(ptr);
}
//operator delete[]实现
void operator delete[](void *ptr)
{
	cout << "operator delete[] addr:" << ptr <<endl;
	free(ptr);
}

问题一:平常我们new与delete都是正常使用的,但是new与delete能混用吗?C++为什么区分单个元素和数组的内存分配和释放呢?

情况1:int类型下将其混用

int *p = new int;
delete[]p;

int *q = new int[10];
delete q;

测试一下:能够混用。对于整型来说,没有构造函数与析构函数,针对于int类型,new与delete功能只剩下malloc与free功能,可以将其混用

情况2:类类型下将其混用

class Test
{
public:
	Test(int data = 10):ptr(new int(data))
	{
		cout << "Test()" << endl;
	}
	~Test()
	{
		delete ptr;
		cout << "~Test()" << endl;
	}
private:
	int *ptr;
};
  1. 先来看一下对单个元素正常使用:
Test *p1 = new Test();
delete p1;
  1. 但是如果我们将单个元素与delete[]混用:
Test *p1 = new Test();
delete[]p1;

测试结果:。new与delete不能再混用了。

  1. 再对数组类型正常使用:
Test *p2 = new Test[5];
delete[]p2;
  1. 最后将数组与delete进行混用:
Test *p2 = new Test[5];
delete p2;

测试结果:

分析

正常情况下,每一个test对象有一个整型成员变量,我们这里分配了5个test对象。delete时先调用析构函数,this指针将正确的对象的地址传入析构函数中,加了[]表示有好几个对象,有一个数组其中每一个对象都要进行析构。但delete真正执行指令时,底层是malloc按字节开辟,并不知道是否开辟了5个test对象的数组,因此还要再多开辟一个4字节来存储对象的个数,假设它的地址是0x100;但是new完之后p2返回的地址是0x104地址,当我们执行delete[]时,会到4字节来取一下对象的个数,将知道了是5个并将这块内存平均分为5份,将其每一份对象起始地址传给相应的析构函数,正常析构,最后将0x100开始的4字节也释放。 而我们p2出错是给用户返回的存对象开始的起始地址,delete p2认为p2只是指向了一个对象,只将Test[0]对象析构,直接从0x104 free(p2),但底层实际是从0x100开辟的,因此崩溃;而p1出错:p1只是单个元素,从0x104开始开辟内存,但是delete[]p1,里面并没有那么多元素,最后还释放了4个字节的存储对象个数的内存(即从0x100释放)因此崩溃。

image-20240413175814411
image-20240413175814411

结论:自定义的类类型,有析构函数,为了调用正确的析构函数,那么开辟对象数组时会多开辟4个字节记录对象的个数,不能混用。

问题二:C++中如何设计一个程序问题?

核心是用new与delete运算符重载接管整个应用的内存管理,对。 检查内存泄露,new操作没有对应的delete,

要求一个类

意思是不能创建堆对象,栈上可以正常创建对象

void test0()
{
    //思考:栈对象的创建需要哪些条件?是不是只要构造函数不为私有就ok?
    Student s1(101, "John");//ok
    Student *pstu = new Student(101, "Mark");//error
}

要达到以上的效果,咱们只需要

  1. 构造函数不为私有

要求一个类只能创建堆对象

创建出堆对象的同时,不能创建栈对象。

void test1()
{
    Student s1(101, "John");//error
    Student *pstu = new Student(101, "Mark");//ok
}
  1. operator new不私有