三、内存管理、指针与堆栈
三、内存管理、指针与堆栈(共 16 项)
1.C/C++中的new和malloc有什么区别?
2.C++中new、delete、malloc、free关系
3.C++中介绍一下delete与 delete[]区别
4.介绍一下newoperator和operatornew的区别
5.main函数执行之前会执行什么? 执行之后还能执行代码吗?
6.堆和栈的区别有什么?
7.程序的局部变量、全局变量、及动态申请的数据分别存储在哪里?
8.编程实现memcopy
9.C/C++中struct的内存对齐与内存占用计算?
10.C/C++中智能指针的定义与作用?
11.C/C++中内存泄漏以及解决方法?
12.空指针和悬空指针的区别?
13.什么是智能指针,它的作用有哪些,智能指针有什么缺点?
14.应用程序在运行时的内存包括代码区和数据区,其中数据区又包括哪些部分?
15.介绍一下c++内存泄漏?
16.C++智能指针在模型服务中如何选择?
以上有什么联系,对C语言内存管理、指针与堆栈?
这 16 道题本质上围绕同一个核心展开:C/C++ 程序如何申请、使用和归还内存,以及指针如何安全地指向这些内存。它们不是孤立的知识点,而是一张从“语法”到“运行时布局”再到“工程实践”的递进网络。下面我先把它们串成一张知识地图,再分层讲清楚彼此之间的联系,以及对你理解 C 语言内存管理、指针与堆栈有什么帮助。
渲染错误: Mermaid 渲染失败: Parse error on line 11: ... delete vs delete[] 内存泄漏 空 -----------------------^ Expecting 'NODE_DESCR', got 'NODE_DEND'
一、先把 16 道题归类,再看联系
| 主题层 | 对应题号 | 核心问题 |
|---|---|---|
| 程序生命周期与内存布局 | 5、14 | 程序从启动到结束,内存分为哪些区域 |
| 动态分配与释放 | 1、2、3、4 | malloc/free 与 new/delete 的区别与配对 |
| 内存错误与泄漏 | 11、12、15 | 什么情况下会泄漏、指针为何变危险 |
| 底层实现细节 | 8、9 | memcpy、结构体内存对齐如何影响读写 |
| 现代 C++ 安全机制 | 10、13、16 | 智能指针如何降低风险,模型服务里怎么选 |
这五层由浅入深:先知道程序启动时内存长什么样(5、14)→ 再知道怎么申请和释放(1、2、3、4)→ 接着知道会犯什么错(11、12、15)→ 再理解底层为什么要对齐、怎么拷贝(8、9)→ 最后用智能指针等工程手段规避错误(10、13、16)。
二、逐层拆解联系
1. 程序启动与内存布局:所有内存问题的舞台
第 5 题(main 前后执行什么)和第 14 题(数据区包含哪些部分)是整张地图的地基。
程序启动时,操作系统会加载可执行文件,并把内存大致划分为:
低地址
+------------------+
| 代码区 (Text) | 可执行指令
+------------------+
| 已初始化数据区 | 全局变量、静态变量(已初始化)
| (Initialized Data)|
+------------------+
| 未初始化数据区 | 全局变量、静态变量(未初始化 / BSS)
| (BSS) |
+------------------+
| 堆 (Heap) | malloc / new 动态申请,向上增长
| |
+------------------+
| 栈 (Stack) | 局部变量、函数参数、返回地址,向下增长
+------------------+
高地址
- 第 5 题:
main之前会执行启动代码(如全局对象构造、__libc_start_main初始化);main之后可以通过atexit注册函数,在进程退出前执行。 - 第 14 题:数据区包括已初始化数据段、BSS 段,再加上堆和栈通常也被归入运行时数据区。
- 第 7 题:局部变量在栈、全局变量在数据区/BSS、动态申请数据在堆,这三道题互相印证。
联系:如果你不知道内存分哪些区,就无法理解为什么局部变量自动释放、为什么 new 出来的要手动 delete、为什么全局变量生命周期贯穿整个程序。
2. 动态分配与释放:C 与 C++ 的分水岭
第 1、2、3、4 题是这一层:
| 特性 | malloc/free |
new/delete |
|---|---|---|
| 来源 | C 标准库 | C++ 运算符 |
| 行为 | 只分配/释放原始内存 | 分配内存 并 调用构造/析构函数 |
| 返回值 | void* |
具体类型指针 |
| 失败处理 | 返回 NULL |
抛出 std::bad_alloc |
| 数组版本 | 无 | new[] / delete[] |
-
第 4 题
new operator与operator new的区别:new operator是关键字,负责“分配内存 + 调用构造函数”。operator new是函数,只负责分配内存,可以被重载。- 对应地,
delete operator负责“调用析构 + 释放内存”,operator delete只释放内存。
-
第 3 题
delete与delete[]:new[]分配的数组必须用delete[]释放,否则只对第一个元素调用析构,导致泄漏或未定义行为。- 对基本类型数组(如
int*),很多实现上delete和delete[]结果相同,但这是未定义行为,不能依赖。
-
第 2 题四者关系:C++ 兼容 C,所以
malloc/free仍可用;但 C++ 对象有构造/析构,必须用new/delete。可以用malloc+placement new手动构造,也可以重载operator new自定义内存池。
联系:这几道题共同说明一个原则——申请与释放必须配对,且配对要精确到语义层面。用 malloc 就用 free,用 new 就用 delete,用 new[] 就用 delete[],混用会导致未定义行为。
3. 内存错误与泄漏:不配对或不小心的后果
第 11、12、15 题讲的是同一件事的不同侧面。
-
内存泄漏(第 11、15 题):申请了内存但没有释放,或释放方式错误。常见场景:
new后没有delete;new[]用delete释放;- 异常发生时,正常释放路径被跳过;
- 循环引用导致引用计数无法归零。
-
空指针与悬空指针(第 12 题):
- 空指针:
ptr == nullptr,指向“无”,访问会崩溃但相对容易排查。 - 悬空指针:
ptr指向的内存已被释放或超出作用域,访问是未定义行为,可能崩溃、可能读出脏数据。
- 空指针:
联系:泄漏是“该释放没释放”,悬空指针是“已经释放却还去用”。它们是一体两面,都源于手动管理内存时生命周期跟踪出错。理解了这一点,就能理解为什么需要智能指针。
4. 底层机制:分配之后真正发生了什么
第 8、9 题把视角拉到机器层面。
- 第 9 题 内存对齐:CPU 按字长访问内存,未对齐访问可能性能下降或直接异常。
struct的成员会按对齐系数填充,导致实际大小大于各成员大小之和。例如:
struct Example {
char a; // 1 字节
int b; // 4 字节,需要 4 字节对齐
char c; // 1 字节
};
// 实际占用通常是 12 字节,而不是 6 字节
这与堆管理、缓存行、CPU 读取效率都相关。对齐也影响 memcpy 的安全性和性能。
- 第 8 题
memcpy实现:要处理内存重叠吗?memcpy不处理重叠,memmove才处理。实现时要考虑:<ul> - 按字节逐拷贝;
- 对齐后按字长拷贝以加速;
- 重叠场景交给
memmove(从高地址或低地址开始拷贝)。
联系:内存对齐解释了为什么 struct 大小会“变大”、为什么强制类型转换有时非法;memcpy 则体现了指针作为“地址”的底层本质——指针操作本质上是在操作一段字节序列。
5. 智能指针:现代 C++ 的工程答案
第 10、13、16 题是 C++ 对前面所有问题的综合治理方案。
| 智能指针 | 管理策略 | 适用场景 |
|---|---|---|
std::unique_ptr |
独占所有权,RAII 自动释放 | 资源唯一归属,推荐默认使用 |
std::shared_ptr |
引用计数,共享所有权 | 多对象共享同一资源 |
std::weak_ptr |
不增加引用计数,观察引用 | 解决 shared_ptr 循环引用 |
std::auto_ptr(C++17 已移除) |
所有权转移 | 已被 unique_ptr 取代 |
-
第 13 题智能指针的缺点:
- 有额外开销(
shared_ptr引用计数、控制块); - 不能解决所有循环引用,需要
weak_ptr; - 性能敏感场景可能不适用;
- 误用(如从原始指针构造多个
shared_ptr)仍会重复释放。
- 有额外开销(
-
第 16 题模型服务中的选择:
- 推理上下文/会话资源:通常用
unique_ptr,生命周期清晰。 - 共享权重/缓存:可用
shared_ptr,但要注意循环引用。 - 跨模块回调/观察者:用
weak_ptr避免循环引用。 - 高频推理路径:避免在热路径频繁创建/销毁
shared_ptr,可考虑对象池或裸指针 + 明确生命周期。
- 推理上下文/会话资源:通常用
联系:智能指针把“申请-释放”的配对责任从程序员转移到编译器生成的 RAII 机制上,直接解决第 11、12、15 题中的人工失误问题。
三、对 C 语言内存管理、指针与堆栈的整体理解
这 16 道题虽然包含 C++ 内容,但底层骨架完全是 C 的。建议你按下面这条主线建立理解:
- 内存是分区使用的:代码区、数据区、堆、栈各司其职。C 程序里,局部变量进栈,动态分配进堆,全局/静态变量进数据区。
- 指针只是地址的别名:指针本身在栈上(如果是局部变量),但它指向的内存可能在堆、栈或数据区。指针的危险性来自“指向谁、是否还有效”。
- 堆需要手动管理生命周期:
malloc申请、free归还;C 没有构造/析构,只有原始内存。C++ 的new/delete在此之上增加了对象语义。 - 栈是自动管理的:函数返回时栈帧销毁,局部变量随之失效,所以不要返回局部变量地址(悬空指针)。
- 错误主要两类:该放的不放(泄漏)、不该用的乱用(悬空/越界/未定义行为)。
- 工程上靠规则降低风险:C 靠约定和代码审查;C++ 可以用智能指针、RAII、容器把手动管理封装起来。
四、一个简短的记忆口诀
启动布局是地基,申请释放要配对;
new/delete 管对象,malloc/free 管内存;
数组 delete 加方括号,混用未定义行为到;
泄漏是忘放,悬空是乱用,智能指针来兜底;
对齐影响大小,memcpy 是底层搬运工。

更多推荐

所有评论(0)