C++内存管理与编译链接
1. 内存模型
*问题类型:
-
C++ 内存分区(栈、堆、全局/静态存储区、常量存储区、代码区)?
分区 存储内容 生命周期 特点 栈 (Stack) 局部变量、函数参数、返回值 函数结束自动释放 自动管理,快速高效 堆 (Heap) 动态分配的内存( new
/malloc
)手动释放( delete
/free
)大容量,分配慢,需手动管理 全局/静态存储区 全局变量、静态变量( static
)程序启动分配,结束释放 初始化为零 常量存储区 字符串常量、 const
全局变量程序运行期间 只读,修改导致段错误 代码区 程序指令(二进制机器码) 程序运行期间 -
栈和堆的区别?
特性 栈 堆 管理方式 编译器自动管理 程序员手动管理 分配速度 极快(移动栈指针) 较慢(搜索可用内存块) 空间大小 较小(默认 MB 级) 较大(接近系统内存上限) 内存碎片 无 可能产生碎片 访问安全 自动边界检查(部分系统) 无保护(易越界) -
内存泄漏(Memory Leak)是什么?如何检测和避免?
-
定义:已分配的堆内存无法被访问且未释放
-
检测方法:
- 工具:Valgrind、AddressSanitizer、智能指针计数
- 日志:重载
new
/delete
记录分配点
-
避免措施:
cppcpp// 使用智能指针(C++11+) auto ptr = std::make_unique<MyClass>(); // 自动释放 // RAII 原则 class ResourceHolder { Resource* res; public: ResourceHolder() : res(new Resource) {} ~ResourceHolder() { delete res; } // 析构时释放 };
-
-
野指针、悬空指针、空指针的区别?
类型 定义 示例 风险 野指针 未初始化的指针(随机地址) int* p; *p = 10;
崩溃/数据损坏 悬空指针 指向已释放内存的指针 int* p = new int; delete p; *p = 20;
未定义行为 空指针 显式指向 nullptr
的指针int* p = nullptr;
安全(可检测) -
new
/delete
和malloc
/free
的区别?特性 new
/delete
malloc
/free
语言 C++ 运算符 C 库函数 构造/析构 调用构造函数/析构函数 仅分配/释放内存 类型安全 返回具体类型指针 返回 void*
(需强转)内存大小 自动计算( new T
)手动指定字节数( sizeof
)失败处理 抛出 std::bad_alloc
返回 NULL
重载 支持运算符重载 不可重载 -
new T[]
和delete[] T
的匹配使用原因?-
内存布局差异:
cppMyClass* arr = new MyClass[3]; // 实际内存布局: // [元素数量] [对象0] [对象1] [对象2] // ^ ^ // | 返回给用户的指针
-
不匹配的后果:
- 使用
delete
而非delete[]
:- 仅调用第一个元素的析构函数
- 错误的内存释放方式(可能少释放前缀计数区)
- 结果:内存泄漏 + 未定义行为(UB)
- 使用
-
2. 编译与链接
*问题类型:
-
C++程序的编译链接过程(预处理、编译、汇编、链接)?
阶段 输入 输出 核心操作 工具 预处理 .cpp
源文件.i
预处理文件宏展开、头文件包含、条件编译 cpp
/gcc -E
编译 .i
文件.s
汇编文件语法分析、语义检查、生成平台相关汇编代码 gcc -S
汇编 .s
汇编文件.o
目标文件汇编指令 → 机器码(二进制) as
链接 多个 .o
文件可执行文件 符号解析、地址重定位、库合并 ld
示例流程:
cppg++ -E main.cpp -o main.i # 预处理 g++ -S main.i -o main.s # 编译 g++ -c main.s -o main.o # 汇编 g++ main.o utils.o -o app # 链接
-
什么是符号表?
-
定义 :目标文件(
.o
)中记录全局符号信息的结构 -
关键内容:
- 导出符号:当前模块定义的全局函数/变量(供其他模块使用)
- 未解决符号:当前模块引用但未定义的函数/变量(需链接时解析)
-
查看工具:
cppnm main.o # 查看符号表 objdump -t lib.a # 静态库符号分析
-
-
动态库(DLL/SO)和静态库(LIB/A)的区别、优点和使用场景?
特性 静态库(.lib/.a) 动态库(.dll/.so) 链接时机 编译时链接到可执行文件 运行时由系统加载 文件组成 目标文件( .o
)集合已链接的共享目标文件 内存占用 每个进程独立副本(内存浪费) 内存中仅一份副本(多进程共享) 更新部署 需重新编译整个程序 替换库文件即可(ABI 兼容前提下) 加载速度 启动快(无运行时加载开销) 启动稍慢(需加载库) 使用场景 小型程序;无依赖环境;嵌入式系统 大型应用;插件系统;公共库 创建示例:
cpp# 静态库 ar rcs libutils.a utils.o # 动态库(Linux) g++ -shared -fPIC -o libutils.so utils.cpp
-
extern "C"
的作用?-
核心功能:禁用 C++ 的名称修饰(Name Mangling),实现 C 语言兼容
-
使用场景:
- C++ 调用 C 库函数
- C 调用 C++ 函数(需反向包装)
-
示例:
cpp// C++ 代码中声明 C 函数 extern "C" { void c_library_function(int param); // 按 C 规则编译 }
名称修饰对比:
- C++ 修饰后:
_Z6funcv
(含参数类型信息) - C 修饰后:
func
(简单函数名)
- C++ 修饰后:
-
-
单一定义规则(ODR)是什么?
-
核心原则:
- 变量/函数:全局范围内最多只能有一个定义
- 类/模板:可在多个编译单元定义,但必须完全一致
-
违规示例:
cpp// file1.cpp int global_var = 10; // 定义 // file2.cpp int global_var = 20; // 重复定义 → 链接错误
-
正确实践:
cpp// header.h extern int global_var; // 声明(非定义) // file1.cpp int global_var = 10; // 唯一定义 // file2.cpp #include "header.h" void use_var() { std::cout << global_var; } // 正确使用
ODR 例外:
- 内联函数/变量(C++17):允许多处定义(需完全相同)
- 模板:实例化时生成唯一定义
-