C++中new与malloc的10点区别

原创
小哥 3年前 (2023-05-24) 阅读数 6 #大杂烩

转自:http://www.codeceo.com/article/cpp-new-malloc-10-tips.html

前言

几周前我去面试了C++对于研发实习岗位,面试官问了一个问题:

new与malloc有什么区别?

这是一个陈词滥调的问题。当时,我回答说new从可用存储区域分配内存,malloc从堆中分配内存;new/delete将调用构造函数/析构函数初始化和销毁对象;operator new/delete可以超载;然后我有力地分析了可用存储区域和堆之间的区别。回来后,我觉得这个问题的答案不是很好,因为new与malloc实际上有很多不同之处。面试时,正好期末考试刚考完,之后没时间组织好几门课程。我今天花了一些时间组织这个问题。

new与malloc的10点区别

  1. 请求的内存的位置

new操作符从 免费存储区(free store) 动态为对象分配内存空间,同时malloc函数从 堆 动态分配内存。免费存储区域是C++基于new运算符的抽象概念,通常由new操作员申请内存,即可用存储区域。堆是操作系统中的一个术语,是指操作系统维护的一块特殊的内存,用于动态分配程序内存,C语言使用malloc使用 从堆中分配内存free释放分配的相应内存。

所以可用存储区域可以是一个堆(问题相当于new是否可以在堆上动态分配内存取决于operator new 的实现细节。免费存储区不仅可以是堆,还可以是静态存储区,这都看operator new为对象分配内存的位置。

特别的,new您甚至不能为对象分配内存! 定位new 的功能可以实现这一点:

new (place_address) type

place_address表示内存块地址的指针。使用上述方法仅使用一个地址呼叫时new使用运算符时,new操作员呼叫特殊operator new这是以下版本:

void * operator new (size_t,void *) //不允许重新定义此版本operator new

这个operator new 不分配任何内存 它只是返回一个指针参数,然后向右new表达式负责place_address在指定地址初始化对象。

2.返回类型安全性

new当运算符的内存分配成功时,它会返回一个指向对象类型的指针,该指针严格匹配对象,不需要类型转换。因此new是符合 类型安全 性爱的经营者。和malloc如果内存分配成功,则返回void ,需要转换为void将指针转换为我们需要的类型。
类型安全在很大程度上等同于内存安全,因为类型安全代码不会尝试访问未经授权的内存区域。大约C++关于类型安全性有很多话要说。

3.内存分配失败时的返回值

new当内存分配失败时,一个bac_alloc异常,它 不会返回NULL ;malloc内存分配失败时返回NULL。
在使用C说到语言,我们已经习惯了malloc确定分配内存后分配是否成功:

int *a  = (int *)malloc ( sizeof (int ));
if(NULL == a)
{
    ...
}
else 
{
    ...
}

从C语言走入C++营地的新手可能会把这个习惯带入C++:

int * a = new int();
if(NULL == a)
{
    ...
}
else
{   
    ...
}

实际上,这样做 这根本没有任何意义 ,因为new我根本不会回来NULL并且该程序可以执行到if该语句已经指示内存分配成功,如果失败,早就抛出异常。正确的方法应该是使用异常机制:

try
{
    int *a = new int();
}
catch (bad_alloc)
{
    ...
}

如果你想顺便了解异常的基础,你可以看看 http://www.cnblogs.com/QG-whz/p/5136883.html C++ 异常机制分析。

4.是否需要指定内存大小

使用new算子申请内存分配时不需要指定内存块的大小,编译器会根据类型信息进行计算malloc您需要明确指出所需内存的大小。

class A{...}
A * ptr = new A;
A * ptr = (A *)malloc(sizeof(A)); //需要显式指定所需的内存大小sizeof(A);

当然,我在这里使用它malloc为我们的自定义类型分配内存不是很合适,请看下一项。

5.是否调用构造函数/析构函数

使用new分配对象内存时,运算符要经历三个步骤:

  • 第 1 步:致电operator new 函数(用于数组operator new[])分配足够大的一块, 原始 用于存储特定类型对象的未命名内存空间。
  • 第 2 步:编译器运行相应的 构造函数 构造对象并传入其初始值。
  • 第 3 部分:对象构造完成后,返回指向对象的指针。

使用delete释放对象内存时,操作员要执行两个步骤:

  • 第 1 步:致电对象的析构函数。
  • 步骤 2:编译器调用operator delete(或operator delete[])释放内存空间的功能。

总之new/delete将调用对象的构造函数/析构函数以完成对象的构造/析构。而malloc不,不会。如果你不介意冗长,你可以看看我的例子:

class A
{
public:
    A() :a(1), b(1.11){}
private:
    int a;
    double b;
};
int main()
{
    A * ptr = (A*)malloc(sizeof(A));
    return 0;
}

在return设置断点,观看ptr参考内存的内容:

可以看出A未调用 的默认构造函数,因为数据成员a,b的值尚未初始化,这就是为什么我提到使用malloc/free来处理C++的自定义类型不合适。事实上,不仅自定义类型,还需要构造的标准库/解构的类型通常是不合适的。

而使用new分配对象时:

int main()
{
    A * ptr = new A;
}

查看程序生成的汇编代码,可以观察到,A调用了 的默认构造函数:

6.处理数组

C++提供了new[]与delete[]专门处理数组类型:

A * ptr = new A[10];//分配10个A对象

使用new[]必须使用分配的内存delete[]释放:

delete [] ptr;

new对数组的支持反映在它能够单独调用构造函数来初始化每个数组元素,以及在释放对象时为每个对象调用析构函数。小心delete[]要与new[]一起使用,否则会发现数组对象部分释放的现象,导致内存泄漏。

至于malloc它不知道你是想在这个内存上放一个数组还是别的东西,反正它只是给你一块原始内存,给你内存的地址。所以如果我们想为数组动态分配内存,还需要手动自定义数组的大小:

int * ptr = (int *) malloc( sizeof(int) );//分配一个10个int元素数组

7.new与malloc我们可以互相打电话吗

operator new /operator delete实现可以基于malloc,而malloc的实现不能调用new。这是写作operator new /operator delete 一个简单的方法,类似于其他版本:

void * operator new (sieze_t size)
{
    if(void * mem = malloc(size)
        return mem;
    else
        throw bad_alloc();
}
void operator delete(void *mem) noexcept
{
    free(mem);
}

8.可以超载吗

opeartor new /operator delete可以超载。标准库已定义operator new函数和operator delete函数的8重载版本:

//这些版本可能会引发异常
void * operator new(size_t);
void * operator new[](size_t);
void * operator delete (void * )noexcept;
void * operator delete[](void *0)noexcept;
//这些版本承诺不会抛出异常
void * operator new(size_t ,nothrow_t&) noexcept;
void * operator new[](size_t, nothrow_t& );
void * operator delete (void *,nothrow_t& )noexcept;
void * operator delete[](void *0,nothrow_t& )noexcept;

我们可以自定义上述任何函数版本,前提是自定义版本必须在全局范围或类范围内。太详细的事情在这里就不讨论了,总之,我们知道我们有足够的自由来超载operator new /operator delete ,确定我们的new与delete如何为对象分配内存以及如何回收对象。

而malloc/free并 不允许重载 。

  1. 可以直观地重新分配内存

使用malloc分配内存后,如果在使用过程中发现内存不足,可以使用realloc该函数重新分配内存以实现内存扩展。realloc首先,确定当前指针引用的内存中是否有足够的连续空间。如果是这样,请就地展开可分配的内存地址并返回原始地址指针;如果空间不够,先按照新指定的大小分配空间,将原始数据从头到尾复制到新分配的内存区域,然后释放原始内存区域。

new没有这种直观的支持工具来扩展内存。

  1. 用于客户处理的内存分配不足

在operator new在抛出异常以反映未满足的要求之前,它将首先调用用户指定的错误处理函数,该函数是 new-handler 。new_handler它是一种指针类型:

namespace std
{
    typedef void (*new_handler)();
}

指向没有参数且没有返回值的函数,这是错误处理功能。要指定错误处理函数,客户端需要调用set_new_handler这是在 中声明的标准库函数:

namespace std
{
    new_handler set_new_handler(new_handler p ) throw();
}

set_new_handler的参数为new_handler指针,指向operator new 无法分配足够内存时要调用的函数。它的返回值也是一个指针,指向set_new_handler在被调用之前正在执行的那个(但即将替换)new_handler函数。

对于malloc当内存不足进行分配时,客户无法编程并决定该怎么做,他们只能观看malloc返回NULL。

总结

替换10将差异组织到一个表中:

特征

new/delete

malloc/free

内存分配的位置

免费存储区

内存分配失败返回值

完整类型指针

void*

内存分配失败返回值

默认引发异常

返回NULL

已分配内存的大小

由编译器根据类型计算

必须显式指定字节数

处理数组

能够处理阵列new版本new[]

用户需要在分配内存之前计算数组的大小

扩展分配的内存

无法直观地处理

使用realloc简单完成

是否互相打电话

是的,这取决于具体情况operator new/delete实现

不可调用new

内存不足分配内存

客户可以指定处理功能或重新设计分配器

无法通过用户代码进行处理

函数重载

允许

不允许

构造函数和析构函数

调用

不调用

malloc我给你的就像一块原始土地,你需要种你想要的东西,自己在土地上播种

而new我帮助您将字段绘制成块(数组),播放种子(构造函数),并提供其他工具供您使用:

当然,malloc并不是说它没有可比性new它们各有其适用性。留C++这种偏重OOP语言, 使用new/delete自然更合适。

感谢您的耐心阅读。

版权声明

所有资源都来源于爬虫采集,如有侵权请联系我们,我们将立即删除