上篇聊了指针运算和数组的关系,有人后台说想看new/delete。

来了。这个话题在面试里考得特别频繁,特别是机器人方向——你写驱动、处理传感器数据、管理点云……哪一样离得开动态内存?

讲个面试故事。

去年面试一个候选人,简历上写着"熟悉C++内存管理"。我问他:"new和malloc有什么区别?"

他说:"new是C++的,malloc是C的。new会调用构造函数,malloc不会。"

我说对。然后追问:"new失败会怎样?"

他想了一下,说:"返回NULL?"

我说:"你确定?"

他犹豫了,改口说:"返回NULL,或者抛出异常?"

我告诉他:"new失败默认抛出std::bad_alloc异常,不会返回NULL。除非你用new(nothrow)。"

他愣了一下,说:"这个我还真不知道。"

讲真,这种问题我见过太多次了。很多人用了好几年new/delete,从来没关注过失败处理。但在面试里,这种细节就是区分度。

new/delete到底干了什么?

先搞清楚底层机制。

new做两件事:分配内存调用构造函数

MyClass* obj = new MyClass(10);

这一行代码,编译器帮你做了两步:

  1. 调用operator new分配足够大的内存
  2. 在这块内存上调用MyClass的构造函数

delete也做两件事:调用析构函数释放内存

delete obj;

先调用obj的析构函数,然后调用operator delete释放内存。

而malloc/free只管分配和释放内存,不管构造和析构。这就是new/delete和malloc/free最本质的区别。

在机器人开发里,你经常要创建传感器对象、初始化控制器,这些都涉及构造函数。如果你用malloc分配内存,构造函数不会被调用,对象就是"半成品",用起来会出各种奇怪的bug。

new失败会怎样?三种处理方式

回到开头那个面试问题。

new失败(比如内存不够了)默认会抛出std::bad_alloc异常。如果你没catch,程序直接崩。

try {
    int* p = new int[1000000000];  // 申请4GB
} catch (const std::bad_alloc& e) {
    cout << "内存分配失败:" << e.what() << endl;
}

但很多时候,你不想用异常来处理。C++提供了另一种方式:new(nothrow)。

#include <new>

int* p = new(nothrow) int[1000000000];
if (p == nullptr) {
    cout << "内存分配失败" << endl;
}

new(nothrow)失败时不抛异常,而是返回nullptr。这种写法更接近C的风格,但在嵌入式系统里很常见——因为很多嵌入式环境不支持异常。

还有一种方式:自定义new_handler。当new失败时,系统会调用你设置的handler函数:

void myNewHandler() {
    cout << "内存不够了,尝试释放一些缓存..." << endl;
    // 释放缓存,然后重试
}

std::set_new_handler(myNewHandler);
int* p = new int[1000000000];

面试的时候,如果你能说出这三种处理方式,面试官会觉得你对内存管理有深入理解。

数组的new/delete:最容易写错的地方

new/delete处理数组时,有个特别容易犯的错:

int* arr = new int[100];
delete arr;    // 错!应该是delete[] arr;

delete和delete[]的区别:delete只析构一个对象,delete[]会析构数组里的每个对象。

对于基本类型(int、float),两者效果一样,因为没有析构函数。但对于自定义类型,差别就大了:

class Sensor {
public:
    ~Sensor() { /* 释放硬件资源 */ }
};

Sensor* sensors = new Sensor[10];
delete sensors;     // 只析构sensors[0],其余9个没析构!
delete[] sensors;   // 正确,10个都析构了

如果忘了加[],9个Sensor对象的析构函数不会被调用,硬件资源就泄漏了。

在机器人开发里,这种错误特别容易出在传感器阵列、关节控制器数组这类场景。我见过一个真实的case,一个同事new了一个电机控制器数组,delete的时候忘了加[],结果9个电机的通信端口没关闭,程序重启后端口被占用,电机全挂了。

new/delete和malloc/free能混用吗?

不能。

new分配的内存必须用delete释放,malloc分配的内存必须用free释放。混用会导致未定义行为。

int* p1 = new int(10);
free(p1);      // 未定义行为!

int* p2 = (int*)malloc(sizeof(int));
delete p2;     // 未定义行为!

为什么?因为new和malloc可能用不同的内存管理策略。new在分配内存时可能会额外记录一些信息(比如对象大小),delete依赖这些信息来正确释放。free不认识这些信息,就会出错。

在机器人开发里,如果你要对接C语言的库(比如某些传感器驱动只提供了C接口),要注意内存管理的一致性。C库用malloc分配的内存在C++里用delete释放,是新手常犯的错误。

在机器人开发里,什么时候用new/delete?

现代C++推荐用智能指针和容器来管理内存,尽量避免裸的new/delete。但在某些场景下,你还是需要用:

场景一:对接C接口。 某些硬件驱动只提供了C接口,你需要手动管理内存。

场景二:性能敏感的场景。 智能指针有引用计数的开销,在高频调用的控制循环里,你可能需要用裸指针来避免这个开销。

场景三:自定义内存池。 后面会专门讲,但核心思路就是自己管理一块内存,用new/delete的方式分配释放。

不过,日常开发中,我强烈建议用std::vector、std::unique_ptr、std::shared_ptr来代替裸的new/delete。安全、方便、不容易出错。

补充一个面试加分点:placement new。它允许你在已经分配好的内存上直接构造对象,不再向系统申请新内存。语法是new (address) MyClass(args)。在机器人开发里,内存池和嵌入式系统经常用到这个技巧。比如你先分配一块大内存,然后用placement new在上面逐个构造对象,避免了反复向系统申请内存的开销。面试时能聊到这个,面试官会觉得你对内存管理的理解不止停留在表面。

再补充一个高频面试追问:operator new和operator delete可以重载吗?答案是可以的。你可以为某个类自定义内存分配策略,比如从预分配的内存池里取内存,而不是走系统的堆分配。这在机器人实时控制系统里特别有用,因为系统malloc的耗时不确定,实时任务不能容忍这种不确定性。重载的写法很简单,在类里声明static void* operator new(size_t size)和static void operator delete(void* ptr)就行。面试官听到你聊到内存池和实时性这两个关键词,基本就知道你有实际工程经验了,不是只会背课本知识。

给正在准备面试的你一点建议

new/delete在面试里考得细。

我建议你搞清楚这几个点:new/delete底层做了什么(分配+构造/析构+释放)、new失败的三种处理方式、数组的delete[]不能忘、new/delete和malloc/free不能混用。

面试的时候,如果你能把这些点讲清楚,还能结合机器人开发中的实际场景举例,面试官会觉得你有工程经验。


如果这篇文章对你有帮助,欢迎点赞、在看、转发三连。 你的支持是我持续更新的最大动力。

「机器人软件开发面试·从入门到精通」连载系列 上一篇:第12篇 指针运算与数组的关系——面试官最爱挖的底层细节 下一篇预告:第14篇 内存泄漏——一个让机器人"跑着跑着就崩了"的元凶

有任何问题欢迎评论区留言,我会尽量回复。

Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐