009、结构体和类
反汇编中的结构体与类
源码层面,struct 和 class 的默认访问权限不同;生成机器码后,public、private、protected 通常不再形成运行时边界。反汇编主要看到:
- 一块对象内存。
- 固定偏移的数据成员。
- 接收对象地址的成员函数。
- 可能存在的虚表、基类子对象和编译器辅助数据。
因此恢复类型时更适合先写“候选结构体”,再逐步确认它是否对应 C++ 类。
对象内存布局
没有继承和虚函数时,对象通常按成员声明顺序安排,但会受内存对齐影响:
1 | struct Record { |
中间空出的字节是填充,结构体末尾也可能补齐到对齐边界。
成员顺序、填充、空基类优化和对象布局都与编译器 ABI 有关。不要把某一次 VS 构建的 sizeof 当成跨平台标准。
从偏移恢复成员
若多个函数都基于同一对象地址访问固定偏移:
1 | mov eax, dword ptr [rcx+8] |
可以暂时恢复为:
1 | struct Candidate { |
不要急于给字段取业务名称。先按 field_08、field_0C 记录,等字符串、API、比较逻辑或数据流提供语义证据后再重命名。
this 指针
非静态成员函数具有隐藏的对象参数 this:
- 32 位 MSVC 的传统
__thiscall常用ECX传递this。 - Windows x64 统一调用约定下,第一个整数/指针参数使用
RCX,成员函数的this也通常位于RCX。 - 其他编译器、平台和显式调用约定可能不同。
识别 this 的证据:
- 调用点把某对象地址放入首参数位置。
- 函数内反复以该地址为基址访问固定偏移。
- 同一批函数共享相容的字段布局。
静态成员
静态数据成员属于类,但不属于某个对象实例。机器码中通常像全局变量一样通过固定模块地址访问,因此不会计入对象大小。
静态成员函数没有 this 参数,在机器码层面更接近普通函数。它是否属于某个类,往往依赖符号、调用上下文或业务关系判断。
空类与成员函数
普通成员函数代码不存放在每个对象中。只有数据成员和实现对象模型所需的隐藏字段占据对象空间。
标准要求不同完整对象具有可区分地址,所以空类对象通常至少占 1 Byte;编译器也可能对空基类应用优化。
对象作为参数
对象按值传递时,ABI 可能:
- 直接用寄存器传递小型平凡对象。
- 把对象副本放入栈参数区域。
- 传入临时副本地址。
- 调用拷贝或移动构造函数。
大型或非平凡对象的表现与编译器、架构和优化选项密切相关。应同时分析调用点与被调函数,不能仅凭某一次复制下结论。
对象作为返回值
小型平凡对象可能直接通过寄存器返回;较大或非平凡对象常通过隐藏的返回缓冲区完成:
1 | 调用者准备结果空间 |
现代编译器还可能应用返回值优化,省去看似必要的临时对象与拷贝。
类型恢复工作流
1 | 1. 选定候选对象地址 |
学习检查清单
| 检查项 | 状态 |
|---|---|
| 能从固定偏移访问恢复候选成员 | 待复盘 |
| 能解释对象内存对齐与填充 | 待复盘 |
| 能识别 x86 与 Windows x64 下常见的 this 传递 | 待复盘 |
| 能说明静态成员为什么不计入对象大小 | 待复盘 |
| 能识别隐藏返回缓冲区的可能性 | 待复盘 |
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Ruiqy~!





















