C++内存布局(vector/虚函数)
今日进度
书籍/资料:《C++ Primer》
核心章节:9.1~9.4,15.1~15.3
实战任务:GDB调试
一、核心知识复盘
1. vector 底层三指针(GCC 实现)
<1>三个指针
_M_start:堆内存起始地址
_M_finish:已存储元素末尾的下一个地址
_M_end_of_storage:整块分配内存的尾部边界
<2>计算公式
size = _M_finish - _M_start (当前元素个数)
capacity = _M_end_of_storage - _M_start (最大容纳量)
<3>扩容完整流程
1)判断_M_finish == _M_end_of_storage触发扩容
2)分配更大连续堆内存(GCC 默认 2 倍扩容,标准无强制倍数)
3)拷贝旧内存所有元素到新内存
4)释放旧堆内存
5)更新三指针指向新地址
6)旧迭代器 / 指针 / 引用全部失效
<4>关键函数区分
| reserve (n) | 仅修改_M_end_of_storage| n>capacity 则扩容 | 预分配内存,只改容量,不改变元素数量 |
| resize (n) | 修改_M_finish| n>capacity 会扩容 | 改变实际元素个数,新增 / 删除元素 |
| shrink_to_fit () | 全部三指针 | 重新分配等于 size 的内存 | 释放多余堆内存,capacity=size |
| swap () | 交换双方三指针 | 不分配内存 | O (1) 交换地址,不拷贝元素 |
<5>emplace_back vs push_back
push_back:外部构造对象,再拷贝 / 移动进容器
emplace_back:直接在容器堆内存原地构造对象,省去拷贝开销
2. 虚函数、vptr、vtable
<1>vptr:存在于每个含虚函数对象的头部,指向本类虚函数表 vtable
<2>vtable:同类对象共用一张,存放所有虚函数的地址
<3>纯虚函数virtual func() = 0;抽象类 vtable 中纯虚函数对应位置存 nullptr; 若实例化抽象类对象,调用纯虚函数会解空指针崩溃,编译器直接禁止创建抽象类对象。
<4>虚析构必要性 基类析构不加 virtual:基类指针 delete 子类对象,仅调用基类析构,子类堆资源泄漏; 加 virtual 后,通过对象 vptr 找到子类 vtable,先执行子类析构,再执行基类析构。
3. list 与 vector 核心区别
list 节点内存离散不连续,无法通过偏移做下标随机访问;vector 连续内存支持[]随机访问,缓存命中率更高。
二、GDB part
通过 GDB 直观验证 vector 容量底层逻辑、虚函数 vptr 内存布局,从汇编内存层面理解 size/capacity、多态、虚析构原理。
实验环境与工程规范:
规范分层工程,源码存放src,编译产物输出bin,使用 Git 本地归档学习代码
实验源码
编译与 GDB 启动流程:
-g参数作用:生成调试符号,是 GDB 查看底层私有成员_M_impl的必要条件;附带 GDB 基础指令讲解。
vector 底层三指针观测
_M_impl._M_start:堆内存起始地址
_M_impl._M_finish:有效数据末尾地址,size = _M_finish - _M_start
_M_impl._M_end_of_storage:堆内存容量边界,capacity = _M_end_of_storage - _M_start
填满容量后 push_back 触发扩容,分配全新堆内存,旧迭代器全部失效。
虚函数 vptr 虚表内存观测:
单步执行验证虚析构多态调用流程,程序正常退出无内存泄漏。
实验代码 Git 归档
调试完成后将源码与可执行文件提交本地 Git 仓库,完成 Day1 学习内容归档。
PS:本次使用的指令
# 切换到仓库根目录 cd ~/Icymemory # 进入源码文件夹 cd src # 创建day1实验目录 mkdir day1 cd day1 # 创建源码文件 touch gdb_demo.cpp #vim编辑 vim gdb_demo.cpp 按下i插入模式,完成后Esc输入:wq保存 # 切回仓库根目录 cd ~/Icymemory # -g 生成GDB调试符号,-Wall 开启代码警告 g++ -g -Wall src/day1/gdb_demo.cpp -o bin/gdb_day1 #启动GDB调试 gdb ./bin/gdb_day1 # 在main函数入口设置断点 b main # 启动程序,触发断点 run # 跳过在线调试信息下载提示 n # 打印vector底层三指针(核心考点1) print vec._M_impl._M_start print vec._M_impl._M_finish print vec._M_impl._M_end_of_storage # 打印对象内存布局,观测头部vptr虚表指针(核心考点2) x/16xg p # 退出GDB调试会话 quit # 回到仓库根目录 ~/Icymemory cd ~/Icymemory # 追踪新增源码与可执行文件 git add src/day1 bin/gdb_day1 # 提交记录,英文备注避免输入法冲突 git commit -m "Day1 GDB test: vector 3 pointers and vptr table memory view"三、Known Issues
class 默认 private 私有继承;struct 默认 public 公有继承。私有继承后,基类 public 成员在子类权限全部变为 private 私有。
swap 交换 vector,会改变 capacity,直接互换_M_start/_M_finish/_M_end_of_storage,两者 size、capacity 互相交换。
const 修饰成员函数作用:函数内禁止修改成员变量;const 对象只能调用 const 成员函数。
无虚析构,基类指针 delete 子类对象会怎样:没有虚析构就不会走 vptr 查表,仅调用基类析构,子类资源无法释放,内存泄漏。
抽象类 vtable 特性:抽象类 vtable 中纯虚函数条目是空指针 nullptr,编译器禁止实例化防止空指针调用崩溃。
emplace_back 优势:原地构造省去拷贝。
