C++中,new与malloc的区别何在?(代码实验向)

在C++中,new与malloc()都可用于在堆中分配一块内存。其中,new是C++的语法,而malloc则来自古老的C语言,二者在使用时有何区别?

new会调用构造函数,而malloc()不会

假设有一个矩形类Rect,定义如下:

复制代码
class Rect {
public:
    Rect() {
        /* 初始化矩形的宽和高为10 */
        mWidth = mHeight = 10;
        std::cout << "Calling Rect(): ";
        display();
    }

    Rect(float width, float height) {
        /* 指定矩形的宽和高,构造一个矩形 */
        mWidth = width;
        mHeight = height;

        std::cout << "Calling Rect(float, float): ";
        display();
    }

    ~Rect() {
        /* 析构函数 */
        std::cout << "Calling ~Rect(): ";
        display();
    }

    void display() {
        /* 输出矩形信息,最后会额外输出一个换行符 */
        std::cout << "Rect(" << mWidth << ", " << mHeight << ")\n";
    }

private:
    float mWidth;  // 矩形的宽
    float mHeight;  // 矩形的高
};

这个类的实现很简单,唯一需要注意的地方在于构造函数和析构函数内都有一条输出,便于我们了解程序是否调用了相关函数。

我们的测试代码长这个样子:

复制代码
int main() {
    std::cout << "[INFO] malloc:\n";
    auto ptrRect1 = (Rect*)malloc(sizeof(Rect));
    ptrRect1->display();

    std::cout << "\n[INFO] new:\n";
    auto ptrRect2 = new Rect;
    ptrRect2->display();

    std::cout << "\n[INFO] program finish:\n";
}

编译运行后,程序的输出如下:

复制代码
[INFO] malloc:
Rect(9.86058e+27, 6.30584e-43)

[INFO] new:
Calling Rect(): Rect(10, 10)
Rect(10, 10)

[INFO] program finish:

可以看到,调用malloc后,没有对分配的那块内存初始化,最后输出矩形的宽和高是内存中保存的随机值;而使用new分配的对象会调用构造函数初始化内存,这就是二者的区别所在。

另外还有一点,这两块在堆上分配的内存没有被回收,虽然在这个小程序中,程序运行结束后会由操作系统负责回收内存,但作为一名专业的编程人员,回收不必要的内存总是一个好习惯,而这,就需要我们考虑delete与free()的区别。

delete会调用析构函数,而free()不会

想必大家都已经猜到了,与new/malloc()的区别类似,delete相较于free, 会调用析构函数。

复制代码
int main() {
    auto ptrRect1 = (Rect*)malloc(sizeof(Rect));
    auto ptrRect2 = new Rect;

    std::cout << "[INFO] free:\n";
    free(ptrRect1);

    std::cout << "\n[INFO] delete:\n";
    delete ptrRect2;

    std::cout << "\n[INFO] program finish:\n";
}

程序的输出如下所示:

复制代码
Calling Rect(): Rect(10, 10)
[INFO] free:

[INFO] delete:
Calling ~Rect(): Rect(10, 10)

[INFO] program finish:

输出结果验证了我们的说法。

对象数组的分配与释放

我们已经知道,malloc/free作为C语言的底层API, 不会为我们做额外的工作,而C++中的new/delete同时承担构造/析构的工作。最后还留有一个问题,对于对象的数组,也遵循相同的规定吗?答案是肯定的,请看下面这个例子:

复制代码
int main() {
    std::cout << "[INFO] create 5 rects:\n";
    auto rect_array = new Rect[5];
    
    std::cout << "\n[INFO] delete rect array:\n";
    delete[] rect_array;
}

输出为:

复制代码
[INFO] create 5 rects:
Calling Rect(): Rect(10, 10)
Calling Rect(): Rect(10, 10)
Calling Rect(): Rect(10, 10)
Calling Rect(): Rect(10, 10)
Calling Rect(): Rect(10, 10)

[INFO] delete rect array:
Calling ~Rect(): Rect(10, 10)
Calling ~Rect(): Rect(10, 10)
Calling ~Rect(): Rect(10, 10)
Calling ~Rect(): Rect(10, 10)
Calling ~Rect(): Rect(10, 10)

从输出中可以看到,使用new Rect[5]分配5个Rect对象时,会为5个Rect对象分别调用一次构造函数;而使用delete[]释放内存时,也会同时调用Rect对象的析构函数。

有的朋友可能好奇:如果不使用delete[] rect_array, 直接使用delete rect_array, 会发生什么呢?既然写到这里,就顺便试一试吧:

复制代码
int main() {
    std::cout << "[INFO] create 5 rects:\n";
    auto rect_array = new Rect[5];
    
    std::cout << "\n[INFO] delete rect array:\n";
    delete rect_array;
}

使用clang编译时会生成一个警告: 使用new[]分配的内存需要使用delete[]释放。但是编译能通过,最后的输出结果为:

复制代码
[INFO] create 5 rects:
Calling Rect(): Rect(10, 10)
Calling Rect(): Rect(10, 10)
Calling Rect(): Rect(10, 10)
Calling Rect(): Rect(10, 10)
Calling Rect(): Rect(10, 10)

[INFO] delete rect array:
Calling ~Rect(): Rect(10, 10)

因此,将delete[]换成delete后,只有第一个Rect对象被释放,程序的状态也出现了异常。

结语

我最早学C语言的时候,以为使用malloc()申请的内存,如果没有调用free()释放,这块内存就不能再用了。所以,一开始不敢使用malloc()这个函数,并且总是感觉自己电脑的内存越来越小。后来才知道,操作系统会在程序运行结束后回收内存,所以初学者完全不必担心,没听说谁写代码把电脑干废的。

在现代C++中,有着智能指针这一更为高级的概念,关键字new/delete已经不被推荐使用。不过这些都是后话了,有时间再研究研究。

posted @   overxus  阅读(34)  评论(0编辑  收藏  举报
评论
收藏
关注
推荐
深色
回顶
展开
点击右上角即可分享
微信分享提示