重载new相关技术疑问:为何需size_t参数及能否混用new/delete对?
好问题!我分两部分来给你拆解清楚:
你提到可以用sizeof(myClass)替代,但这只在当前类没有子类、且仅创建当前类对象的场景下成立,一旦涉及继承或多态,这个思路就会彻底失效。
举个实际的例子:假设你有一个基类Base,并在Base中重载了operator new。如果你的重载函数硬编码用sizeof(Base)分配内存,那么当你创建子类Derived的对象(new Derived)时,调用的依然是Base的重载new——这时候分配的内存大小只够装下Base,完全容纳不下Derived的额外成员,直接触发内存越界,程序大概率崩溃。
标准规定operator new的签名必须是void* operator new(size_t size),核心原因就是编译器会自动传递实际要创建的对象的真实大小。比如上面的例子,创建Derived对象时,编译器会把sizeof(Derived)作为size参数传入重载函数,这样你的代码就能分配足够的内存,不管是基类还是子类对象都能正确处理。
另外,哪怕不考虑继承,重载new的设计初衷就是要作为通用的内存分配入口——全局operator new需要适配任意类型的分配需求,类成员的operator new也需要支持子类的扩展,size_t参数让它具备了这种通用性,而不是绑定到某一个类的固定大小。
答案非常明确:绝对不能混用,这属于C++标准定义的未定义行为,具体的弊端主要有这几点:
析构函数调用不完整:当你用
new[]创建对象数组时,编译器会在分配的内存块头部额外存储数组的元素个数(这是主流编译器的通用实现)。delete[]会读取这个数字,然后依次调用每个元素的析构函数。如果误用delete释放new[]的数组,只会调用第一个元素的析构函数,剩下的元素的析构函数完全不会执行——如果你的类持有动态内存、文件句柄、网络连接等资源,这会直接导致资源泄漏。内存释放错误:部分编译器中,
new[]返回的指针并不是实际分配的内存起始地址(因为前面有存储元素个数的区域)。delete会直接释放你传入的指针,而非真正的起始地址,这会导致堆损坏,程序直接崩溃,或者出现难以排查的隐性内存错误(比如后续内存操作时莫名其妙崩溃)。
反过来,用delete[]释放new创建的单个对象时,delete[]会尝试读取内存块头部的元素个数,但这里根本没有合法数值,会触发不可预测的行为——可能直接崩溃,可能破坏堆结构,甚至可能看起来“正常运行”但埋下隐患,后续随时出问题。
总结:new必须和delete配对,new[]必须和delete[]配对,这是C++的基础规则,违反它的后果完全不可控。
内容的提问来源于stack exchange,提问作者tkacper

