new、delete、malloc、free
C++ 内存管理是涉及内存申请、释放、智能指针、内存安全(野指针、悬空指针)、内存池等
# 1. 什么是内存泄漏?如何检测和避免内存泄漏?
- 解答方向:
- 内存泄漏: 动态分配的内存未被释放,导致内存无法被重用。
- 检测: 使用工具如 Valgrind、AddressSanitizer 等来检测内存泄漏。
- 避免: 通过良好的编码习惯,使用智能指针(如
std::unique_ptr
、std::shared_ptr
)来管理动态内存。
# 2. C++ 中的栈和堆的区别?
- 解答方向:
- 栈: 自动分配和释放,存储局部变量,生命周期由函数作用域决定。
- 堆: 由程序员手动分配和释放,存储动态分配的内存,生命周期由程序员决定。
# 3. 解释 new
和 malloc
的区别。
- 解答方向:
new
是 C++ 的运算符,分配内存并调用构造函数。malloc
是 C 的函数,仅分配内存,不调用构造函数。new
返回指定类型的指针,malloc
返回void*
指针,需要强制类型转换。new
失败时抛出std::bad_alloc
异常,malloc
失败时返回NULL
。
# 4. New底层实现
在 C++ 中,new
运算符用于在堆上分配内存并构造对象。new
运算符底层的实现涉及内存分配和对象初始化两个主要步骤。
4.1 内存分配
- 当你使用
new
运算符时,它首先调用operator new
函数,该函数负责在堆上分配足够的内存以容纳指定类型的对象。 - 默认情况下,
operator new
函数调用标准库中的malloc
函数来执行内存分配。malloc
函数会请求操作系统分配一块内存,并返回这块内存的首地址。 - 如果内存分配失败,
operator new
会抛出std::bad_alloc
异常,而不是返回NULL
。
示例实现:
void* operator new(std::size_t size) {
if (void* p = std::malloc(size)) {
return p;
}
throw std::bad_alloc(); // 分配失败,抛出异常
}
1
2
3
4
5
6
2
3
4
5
6
4.2 对象构造
- 内存分配成功后,
new
运算符会调用对象的构造函数。构造函数使用上一步分配的内存地址,将对象初始化。 - 如果对象的构造函数中有复杂的初始化逻辑(如分配资源),这些操作会在这一阶段完成。
整个过程可以用以下代码段表示:
// 使用 new 创建对象的过程
MyClass* obj = new MyClass();
1
2
2
其底层类似于:
void* mem = operator new(sizeof(MyClass)); // 分配内存
MyClass* obj = new(mem) MyClass(); // 在分配的内存上调用构造函数
1
2
2
4.3 失败处理
- 如果内存分配或构造函数失败(如构造函数抛出异常),
new
运算符将释放之前分配的内存,并将异常向上传播。这确保了没有内存泄漏或未初始化的对象。
4.4 对齐要求
new
操作符还需要考虑内存对齐。某些平台要求特定类型的数据存储在特定对齐的内存地址上,以提高访问效率。
4.5 自定义
operator new
- C++ 允许用户重载
operator new
,以实现自定义内存分配策略。例如,你可以使用内存池来优化分配或在嵌入式系统中使用特殊的内存管理策略。
示例:
void* MyClass::operator new(std::size_t size) {
// 自定义内存分配逻辑
void* p = custom_alloc(size);
if (!p) throw std::bad_alloc();
return p;
}
1
2
3
4
5
6
2
3
4
5
6
# 6. 什么是悬空指针?如何避免?
- 解答方向:
- 悬空指针: 指向已释放的内存区域的指针。
- 避免: 在释放内存后将指针置为
NULL
,使用智能指针替代原生指针。
# 7. 什么是内存对齐?为什么需要内存对齐?
- 解答方向:
- 内存对齐: 是指将数据存储在内存中的某些特定地址上,以提高访问速度。
- 需要: 不同的处理器要求数据以特定的边界对齐,否则会导致性能下降或无法访问。
# 8. C++ 中的浅拷贝和深拷贝是什么?有什么区别?
- 解答方向:
- 浅拷贝: 拷贝对象的值,但不复制动态分配的内存,仅复制指针。
- 深拷贝: 拷贝对象及其动态分配的内存,复制内存内容。
- 区别: 浅拷贝可能导致多个对象指向同一块内存,导致内存泄漏或悬空指针;深拷贝则更安全。
# 9. 什么是双重释放?如何防止?
- 解答方向:
- 双重释放: 对同一块内存调用
delete
或free
两次,会导致程序崩溃或未定义行为。 - 防止: 释放内存后立即将指针置为
NULL
,使用智能指针管理内存。
- 双重释放: 对同一块内存调用
# 10. C++ 中内存池(Memory Pool)是什么?如何实现?
- 解答方向:
- 内存池: 预先分配一大块内存,然后按需从中分配小块内存,减少频繁调用
new
和delete
的开销。 - 实现: 维护一个可用内存块的列表,每次分配时从列表中取出,释放时将内存块归还到列表中。
- 内存池: 预先分配一大块内存,然后按需从中分配小块内存,减少频繁调用
# 11. 如何实现自定义的内存管理器?
- 解答方向:
- 通过重载
new
、delete
运算符或创建内存池来管理内存分配和释放,提升程序性能。 - 可以实现内存复用、减少碎片、优化特定场景下的内存分配等。
- 通过重载
# 12. 谈谈 RAII(Resource Acquisition Is Initialization)在内存管理中的作用。
- 解答方向:
- RAII: 资源获取即初始化,资源(如内存)在对象创建时获取,在对象销毁时释放。
- 作用: 自动管理资源生命周期,防止资源泄漏。例如,智能指针就是 RAII 的应用。
# 13. C++ 中常见的内存管理错误有哪些?
- 解答方向:
- 内存泄漏: 动态分配的内存未被释放。
- 悬空指针: 使用了已释放的内存。
- 双重释放: 对同一块内存调用
delete
或free
两次。 - 野指针: 指针未初始化即使用。
# 14. 什么时候应该使用 std::allocator
?
- 解答方向:
std::allocator
: 是 C++ 标准库中提供的内存分配器,用于在容器中自定义内存管理。- 使用场景: 当需要优化容器的内存分配策略或在特定场景下管理内存时,可以使用
std::allocator
来替代默认的分配器。
# 15. new 和 delete 的工作机制是什么?
- 解答方向:
new
调用operator new
分配内存,然后调用构造函数初始化对象。delete
调用析构函数销毁对象,然后调用operator delete
释放内存。- 可以重载
operator new
和operator delete
以自定义内存分配行为。
编辑 (opens new window)
上次更新: 2024/09/13, 11:59:12