
本文共 10532 字,大约阅读时间需要 35 分钟。
在阅读本篇文章之前,我们先带着几个问题思考一下:
- free可以对new的空间进行释放吗?
- delete可以释放new出的数组空间吗?
- 什么情况下可以使用free或delete释放,却不能使用delete[]释放?
- 什么情况下可以free、delete、delete[] 三种方法进行资源的释放?
让我们带着疑问阅读了下面的内容,希望在您阅读了以下内容后,能够有所收获,而以上几个问题的答案就出现在本篇文章中。
文章目录
一、malloc函数只申请资源,不调用构造
1.1 空指针访问类成员方法
示例一:试问以下代码会不会产生错误?如果产生了错误,请指明是哪几行,并说明错误原因。
class Object{ private: int value;public: Object(int x = 0) :value(x) { cout << " construct object :" << this << endl; } ~Object() { cout << "deconstruct object : " << this << endl; } static void show() { cout << "0bject : : show" << endl; } void print() { cout << "Object : : print" << endl; } void print_value() { cout << value << endl; }};int main(){ Object* op = nullptr; op->show(); op->print(); op->print_value(); return 0;}
分析:
在以上代码中出现了三个类成员函数函数,依次对齐分析如下:- show():该函数是静态成员方法,可以不依赖对象调用,因此可以说与对象无关。那么
op->show();
不会产生错误。 - print():该函数是类成员方法,但该方法的实现中并没有访问类对象的成员,我们都知道类成员方法的存储是不占用类对象的存储空间的,因此’op->print();` 也不会缠身错误。
- print_value():该函数与print()一样同属类成员方法,而在该方法的实现中访问了类对象的成员value。具体行为是,通过this指针解引用访问value。而我们可以看到在main函数中,
Object* op = nullptr;
,那么当函数执行到op->print_value();
时就会访问 0x00000000位置(NULL),该位置属于系统保留区不提供访问,因此会造成程序崩溃。
1.2 使用malloc函数申请资源
示例二:使用malloc申请资源,可以看到在类的构造函数中存在默认值,而malloc申请资源是并不能进行赋值。
那么试问以下的代码会不会出现错误?如果会出现错误请说明原因,如果不会出现错误请分析输出结果。
// 注:此处类的设计,与实例一相同class Object{ private: int value;public: Object(int x = 0) :value(x) { cout << " construct object :" << this << endl; } ~Object() { cout << "deconstruct object : " << this << endl; } static void show() { cout << "0bject : : show" << endl; } void print() { cout << "Object : : print" << endl; } void print_value() { cout << value << endl; }};int main(){ Object* op = nullptr; op = (Object*)malloc(sizeof(Object)); // malloc op->show(); op->print(); op->print_value(); return 0;}
分析:以上代码不会出现错误,其中op->print_value()
输出结果为 -842150451
。
0xCDCDCDCD
。 1.3 空间与对象间的赋值
由于malloc申请的资源没有调用构造函数,没有生成完整的对象,因此通过malloc申请的资源只能是一片空间而非一个对象。
示例三:对象给空间赋值。由于该空间是由一个 Object 类型指针所指向的,因此在通过->
成员访问符访问类成员是可以的,而类中存在有隐藏的赋值函数。因此,该空间是可以合法调用Object::operator=()
函数的。
试问:在以下代码中,是否存在错误,如果存在错误请指明错误原因,如果不存在错误请分析输出结果。
// 注:此处类的设计,与实例一相同class Object{ private: int value;public: Object(int x = 0) :value(x) { cout << " construct object :" << this << endl; } ~Object() { cout << "deconstruct object : " << this << endl; } static void show() { cout << "0bject : : show" << endl; } void print() { cout << "Object : : print" << endl; } void print_value() { cout << value << endl; }};int main(){ Object obj(10); Object* op = (Object*)malloc(sizeof(Object)); // malloc *op = obj; op->show(); op->print(); op->print_value(); return 0;}
分析:程序运行并无明显错误,输出结果中op->print_value();
输出了与 obj对象 中的value值相同的结果。
1.4 避免空间与对象间进行赋值
在1.3中提到,空间与对象间赋值存在隐患,下面将演示这中场景。
示例四:将类中的某方法设置为虚函数。我们都知道一旦类中某个成员方法设计为虚函数,那么该类就要生成对应的虚表,存储虚函数的地址信息。
试问:观察以下代码,是否存在错误,如果存在请指出错误原因,如果不存在请分析输出结果。
// 注:此类的设计中,把print设置为虚函数class Object{ private: int value;public: Object(int x = 0) :value(x) { cout << " construct object :" << this << endl; } ~Object() { cout << "deconstruct object : " << this << endl; } static void show() { cout << "0bject : : show" << endl; } virtual void print() { cout << "Object : : print" << endl; } void print_value() { cout << value << endl; }};int main(){ Object obj(10); Object* op = (Object*)malloc(sizeof(Object)); // malloc *op = obj; op->show(); op->print(); op->print_value(); return 0;}
分析:程序运行到op->print();
语句时崩溃。
op->print();
时由于无法通过虚表指针找到 print 函数的地址而引发程序的崩溃。 1.5 使用重定位new,在已有空间的基础上构建对象
在1.4中由于没有调用构造函数这一步操作,对象并不完整,因此只能当做一片空间来使用。而只有在空间的基础上调用了构造函数后才能称之为对象。
因此我们可以这样做,使用operator new进行重定位构建对象。
示例五:代码如下
class Object{ private: int value;public: Object(int x = 0) :value(x) { cout << " construct object :" << this << endl; } ~Object() { cout << "deconstruct object : " << this << endl; } static void show() { cout << "0bject : : show" << endl; } virtual void print() { cout << "Object : : print" << endl; } void print_value() { cout << value << endl; }};int main(){ Object obj(10); Object* op = (Object*)malloc(sizeof(Object)); // malloc new(op) Object(20); // 重定位new,在已有空间的基础上构建对象 op->show(); op->print(); op->print_value(); return 0;}
输出结果:

二、new关键字先申请空间,后调用构造
有关new与delete的使用,相信大家都很熟悉了,这里就不再赘述。关于new的使用方法可以参考《》这篇文章。
对于这对关键字,主要有以下两种用法:
- 对于单个对象
- new:申请单个对象
- delete:销毁单个对象
- 初始化:可以通过
new type(val)
的形式初始化。
- 对于多个对象
- new[] :申请对象数组
- delete[] :销毁对象数组
- 初始化:可以使用
new type[cnt]()
的方式令所有对象初始化为0,或使用new type[cnt]{v1, v2, v3 ...}
的方式对每个对象初始化。
2.1 对于内置类型,使用new[] 申请的资源可以使用 delete 或 delete[] 释放资源
一般来说,使用new[] 创建的数组对象都需要使用delete[] 的方式进行释放。而对于内置类型来说,使用 delete 或 delete[] 的释放方式都可以。
int main(){ int* pi = new int(); int* pis = new int[5]{ 1,2,3,4,5}; delete pi; delete pis; return 0;}
甚至,使用 free() 的方式释放也是可以的。
free(pi);free(pis);
需要注意的是,对于内置类型来说,他们不存在构造函数和析构函数,在使用new时也只是申请了空间,而后忽略其调用构造函数这一步,而delete在释放资源后,也不会调用析构函数。
而对于使用 new[] 的方式申请的数组为什么可以使用 delete 或 free() 来释放资源这件事,请继续往下看。
2.2 类中没有析构函数,就可以使用 delete 释放 new[] 申请的对象数组
例如:此代码不会产生任何问题。
class Object{ public: Object() { } //~Object();};int main(){ Object* pobj = new Object[10](); delete pobj; return 0;}
可以看到我们使用new[] 创建了堆区数组,不过由于我们的类中不存在析构函数,因此调用delete关键字析构数组不会产生任何问题。这就等同于int* p = new int[10]; delete p;
不会产生任何问题一样。
我们都知道,类中有隐藏的几个函数,我们不实现时编译器会自动帮我们实现。而实际上这样的说法是不严谨的,就如上面的例子而言,编译器的确实现了一个Object类的析构函数,但并没有进行实际的调用这步操作。
而如果该类继承的基类中有析构函数,或者自身的成员对象有析构函数,最终都会有实际的析构函数的调用这一步操作。那么此时使用delete 释放数组资源就会出错。
class Base{ public: ~Base() { }};class Object :public Base // 基类中有析构{ };int main(){ Object* pobj = new Object[10](); delete pobj; // errror return 0;}
class Base{ public: ~Base() { }};class Object{ private:Base bs; // 成员对象有析构函数};int main(){ Object* pobj = new Object[10](); delete pobj; // errror return 0;}
问题4:什么情况下可以free、delete、delete[] 三种方法进行资源的释放?
也就是说,只要我们的类最终不调用析构函数,delete 就可以实现对单个堆对象或多个堆对象使用,甚至是使用 free() 释放也是可以的。
class Object{ public: Object() { cout << "c" << endl; } //~Object() { cout << "d" << endl; } virtual void show() { cout << this << endl; }};int main(){ Object* pobj = new Object[10](); // 以下任选其一皆可 delete pobj; free(pobj); delete []pobj; return 0;}
问题3:什么情况下可以使用free或delete释放,却不能使用delete[]释放?
而当我们类中存在析构时,free 与 delete 均可对单个的堆对象释放资源,只是free不会调用对象的析构。而我们此时使用 delete[] 释放数组资源就会出错。
class Object{ public: Object() { cout << "c" << endl; } ~Object() { cout << "d" << endl; } virtual void show() { cout << this << endl; }};int main(){ Object* pobj = new Object(); // 前两个皆可 delete pobj; free(pobj); // 可以释放资源,但不会调用析构函数 delete []pobj; // errror return 0;}
2.3 new[] 资源的申请与 类的析构函数 之间的关系
而究其原因是因为在类中存在析构函数时,申请的资源中有额外的4个字节空间,代表此次申请的对象个数。 delete[] 会依据这对象个数对空间进行逐个的释放。

0xFDFDFDFD
当做对象个数而进行多次的释放,从而引发错误。 
问题2:delete可以释放new出的数组空间吗?
答案当然是可以的,根据上面的分析可知,只要类中不存在析构函数,delete在释放资源时就不会发生错误。即当申请的对象空间中,没有额外的空间用于统计对象个数的标志位,此时便可以使用 delete 释放该篇连续的数组空间。

问题1:free可以对new的空间进行释放吗?
解答:一般来说不可以,参考《》中提到new运算符为来自称为“自由存储”的池中的对象分配内存,而malloc明确定义是从“堆”上申请空间。
例如,new可以从已有变量的空间上分配资源。
int main(){ int a = 0x00010001; short* psh = new(&a) short; cout << a << " " // 65537 << *psh << endl; // 1 *psh = 0; cout << a << " " // 65536 << *psh << endl; // 0}
但是在某些情况下,free()是可以释放new的资源的。比如new在堆区申请的不带标志位的空间可以使用free()释放,参考前文提到的示例。
因为尽管使用不同的内存分配算法来实现new和malloc是合法的,但是在大多数系统上,new的内部是使用malloc实现的,不会产生系统级的差异。
附:new/delete与malloc/free的异同(参考StackOverflow)
new/delete是C++式的写法,malloc/free是C式写法,并且相对于malloc来说new大致具有以下的优点:
- 更安全,主要表现在:
- new内存分配失败,则会引发异常,并非返回一个NULL指针。
- new是类型安全的,返回确切的类型,而非(void*)指针。
- new通过调用该对象的构造函数来构造对象,而并非通过(type*)进行类型转换。
- 更方便:主要表现在:
- 申请对象时自动调用构造函数,释放对象时自动调用析构函数
- new是运算符,可以重载,而malloc是函数,不能重载
- new可以在申请时做初始化,而malloc只能先申请空间后进行赋值。
- 使用new[] 分配数组,比malloc更直观。
除此之外,new还能做到很多malloc无法完成的事,具体可以参考一下几篇文章。
以下引用自上述连接,有关new/delete与malloc/free的相关资料。

- Allocate/release memory
- Memory allocated from ‘Free Store’
- Returns a fully typed pointer.
- new (standard version) never returns a NULL (will throw on failure)
- Are called with Type-ID (compiler calculates the size)
- Has a version explicitly to handle arrays.
- Reallocating (to get more space) not handled intuitively (because of copy constructor).
- Whether they call malloc/free is implementation defined.
- Can add a new memory allocator to deal with low memory (set_new_handler)
- operator new/delete can be overridden legally
- constructor/destructor used to initialize/destroy the object
malloc/free
- Allocates/release memory
- Memory allocated from ‘Heap’
- Returns a void*
- Returns NULL on failure
- Must specify the size required in bytes.
- Allocating array requires manual calculation of space.
- Reallocating larger chunk of memory simple (No copy constructor to worry about)
- They will NOT call new/delete
- No way to splice user code into the allocation sequence to help with low memory.
- malloc/free can NOT be overridden legally
Table comparison of the features:
Feature | new/delete | malloc/free --------------------------+--------------------------------+------------------------------- Memory allocated from | 'Free Store' | 'Heap' Returns | Fully typed pointer | void* On failure | Throws (never returns NULL) | Returns NULL Required size | Calculated by compiler | Must be specified in bytes Handling arrays | Has an explicit version | Requires manual calculations Reallocating | Not handled intuitively | Simple (no copy constructor) Call of reverse | Implementation defined | No Low memory cases | Can add a new memory allocator | Not handled by user code Overridable | Yes | No Use of (con-)/destructor | Yes | No
Technically memory allocated by new comes from the ‘Free Store’ while memory allocated by malloc comes from the ‘Heap’. Whether these two areas are the same is an implementation detail, which is another reason that malloc and new can not be mixed.
发表评论
最新留言
关于作者
