当前位置: 首页 > news >正文

【c++面向对象编程】第26篇:对象的内存模型:成员变量与成员函数的存储分离

目录一、一个简单的问题二、成员变量的存储非静态成员变量静态成员变量三、成员函数的存储非虚成员函数虚函数的影响四、内存对齐与对象大小对齐规则简化版示例同样的成员不同顺序大小不同五、对象大小计算规则总结计算对象大小的步骤六、完整例子观察内存布局七、常见误区1. 认为成员函数存储在对象中2. 忽略对齐导致的大小计算错误3. 认为空类不占内存4. 混淆静态成员变量和全局变量八、这一篇的收获一、一个简单的问题先看这段代码猜猜输出cpp#include iostream using namespace std; class Empty { }; class Data { int x; double y; char c; }; class WithFunc { int x; public: void func1() {} void func2() {} }; int main() { cout sizeof(Empty): sizeof(Empty) endl; cout sizeof(Data): sizeof(Data) endl; cout sizeof(WithFunc): sizeof(WithFunc) endl; return 0; }典型输出64位系统textsizeof(Empty): 1 sizeof(Data): 16 sizeof(WithFunc): 4关键观察空类占用1字节C保证每个对象有唯一地址WithFunc的大小是4字节就是int x的大小——成员函数不占用对象内存二、成员变量的存储非静态成员变量每个对象都有自己独立的一份非静态成员变量cppclass Point { public: int x; int y; static int count; // 静态成员不属于对象 }; int Point::count 0; int main() { Point p1, p2; p1.x 10; p2.x 20; // p2.x 独立于 p1.x cout sizeof(Point) endl; // 8两个int各4字节 cout p1.x endl; // p1.x的地址 cout p2.x endl; // p2.x的地址不同 }内存布局textp1 对象: [ x (4字节) ][ y (4字节) ] p2 对象: [ x (4字节) ][ y (4字节) ] 静态成员 count: 存储在全局数据区所有对象共享静态成员变量静态成员变量不存储在对象中而是存储在全局/静态数据区类似全局变量。所有对象共享同一份。三、成员函数的存储非虚成员函数成员函数的代码存储在代码区text segment所有对象共享。编译器在编译时将成员函数转换成普通函数隐式传递this指针。cppclass Demo { int value; public: void setValue(int v) { value v; } int getValue() const { return value; } }; // 编译器大致转换成 void setValue(Demo* this, int v) { this-value v; } int getValue(const Demo* this) { return this-value; }结论成员函数不占用对象内存。对象大小只由非静态成员变量决定加上对齐填充和vptr。虚函数的影响如果类有虚函数或继承自包含虚函数的类对象中会多一个vptr指针8字节在64位系统。cppclass Base { int x; public: virtual void func() {} // 有虚函数 }; class Derived : public Base { int y; }; cout sizeof(Base) endl; // 16 (vptr 8 x 4 对齐4) cout sizeof(Derived) endl; // 24 (vptr 8 x 4 y 4 对齐8)四、内存对齐与对象大小内存对齐是编译器为了CPU高效访问而做的优化。对象大小不一定等于成员变量大小的简单相加。对齐规则简化版每个类型有自己的对齐要求通常是其大小的倍数结构体的总大小是其最大成员对齐要求的整数倍成员按声明顺序排列但可能插入填充字节示例同样的成员不同顺序大小不同cpp#include iostream using namespace std; struct A { char c; // 1字节 int i; // 4字节 short s; // 2字节 }; struct B { int i; // 4字节 short s; // 2字节 char c; // 1字节 }; int main() { cout sizeof(A): sizeof(A) endl; // 12 cout sizeof(B): sizeof(B) endl; // 8 return 0; }A的布局text偏移0: char c (1字节) 偏移1-3: 填充 (3字节) ← 为了让int对齐到4字节 偏移4-7: int i (4字节) 偏移8-9: short s (2字节) 偏移10-11: 填充 (2字节) ← 让整个结构体是4的倍数 总大小: 12B的布局text偏移0-3: int i (4字节) 偏移4-5: short s (2字节) 偏移6: char c (1字节) 偏移7: 填充 (1字节) ← 让整个结构体是4的倍数 总大小: 8教训把大的成员放在前面小的放在后面通常能减小对象大小。五、对象大小计算规则总结组成部分是否占用对象内存说明非静态成员变量✅ 是每个对象独立存储静态成员变量❌ 否存储在静态区对象共享成员函数非虚❌ 否代码存储在代码区虚函数表指针vptr✅ 是如果类有虚函数每个对象多一个vptr内存对齐填充✅ 是为了对齐而插入的空白字节计算对象大小的步骤累加所有非静态成员变量的大小如果类有虚函数加上vptr大小64位系统8字节考虑内存对齐总大小调整为最大成员对齐的整数倍cppclass Sample { char a; // 1字节 virtual void f() {} // 触发vptr int b; // 4字节 static int c; // 不计入对象大小 }; int Sample::c 0; // 计算vptr(8) a(1) 填充(3) b(4) 16 cout sizeof(Sample); // 1664位六、完整例子观察内存布局cpp#include iostream using namespace std; // 辅助函数打印对象内存的十六进制表示 void printMemory(const void* obj, size_t size) { const unsigned char* bytes static_castconst unsigned char*(obj); for (size_t i 0; i size; i) { printf(%02x , bytes[i]); } cout endl; } class Simple { public: char c; int i; short s; }; class WithVirtual { public: char c; int i; virtual void func() {} virtual void func2() {} }; class Empty { }; int main() { cout 类型大小 endl; cout sizeof(Simple): sizeof(Simple) endl; cout sizeof(WithVirtual): sizeof(WithVirtual) endl; cout sizeof(Empty): sizeof(Empty) endl; cout \n Simple对象内存 endl; Simple s; s.c A; // ASCII 0x41 s.i 0x12345678; s.s 0xABCD; printMemory(s, sizeof(s)); cout \n WithVirtual对象内存 endl; WithVirtual v; v.c B; v.i 0x87654321; printMemory(v, sizeof(v)); cout \n 多个对象的地址 endl; Simple s1, s2; cout s1地址: s1 endl; cout s2地址: s2 endl; cout s1.s地址: (s1.i) endl; cout s2.s地址: (s2.i) endl; cout \n 成员函数地址所有对象共享 endl; cout Simple::printMemory地址: (void*)Simple::printMemory endl; // 注意成员函数地址不是对象的一部分 return 0; }七、常见误区1. 认为成员函数存储在对象中cpp// ❌ 错误理解 // 每个对象不会存储一份函数代码 // 函数代码只有一份通过this指针区分操作哪个对象2. 忽略对齐导致的大小计算错误cppstruct Misaligned { char a; double b; // 需要8字节对齐 }; // 实际大小是16a7填充b不是93. 认为空类不占内存cppEmpty e1, e2; cout (e1 e2); // false不同的对象必须有不同地址 // 所以空类占1字节或更多取决于编译器4. 混淆静态成员变量和全局变量静态成员变量存储在静态区但作用域在类内。八、这一篇的收获你现在应该理解成员函数不占对象内存函数代码所有对象共享通过this区分对象大小由非静态成员变量决定加上vptr如果有虚函数和对齐填充内存对齐CPU访问效率的优化可能导致对象比成员总和大布局优化把大成员放前面可以减小对象大小静态成员不属于对象存储在全局/静态区 小作业定义几个结构体包含char、short、int、double类型成员用不同顺序排列sizeof观察大小变化。设计一个“最优”布局和最差布局验证对齐规则。下一篇预告第27篇《空类的大小为什么是1——C对象标识的秘密》——深入探讨空类占1字节的原因以及带虚函数的空类为什么是864位理解对象标识和唯一地址的设计哲学。
http://www.gsyq.cn/news/1299035.html

相关文章:

  • Python自动化抓取B3金融数据:逆向工程与数据清洗实战
  • 拆解GoTenna:剖析蓝牙与Sub-1GHz射频混合通信硬件设计
  • 告别3D-DNA的卡顿:用Chromap+Yahs快速搞定植物Hi-C辅助组装(附完整代码)
  • CUDA自动调优工具:原理、实现与工程实践
  • MoviePilot批量重命名终极指南:5步打造完美媒体库
  • Gempy实战:如何将地质剖面图与Matplotlib/VTK结合,做出炫酷的3D可视化成果?
  • 开发Agent应用时如何通过Taotoken集成OpenClaw工具流
  • HAProxy 配置超时参数 timeout connect 和 server 区别在哪
  • 基于CircuitPython的巨型机械键盘:从嵌入式开发到定制输入设备实践
  • 基于RP2040与Santroller固件,复活旧吉他控制器玩转现代音游
  • AEUX终极指南:3步实现从设计到动画的无缝转换工作流优化
  • 从零打造3x3x3 NeoPixel LED立方体:硬件焊接与Arduino编程全指南
  • BepInEx:5个步骤轻松实现Unity游戏插件开发,让游戏焕然一新![特殊字符]
  • 基于WebRTC的P2P远程控制工具vibe-remote部署与实战
  • 基于Adafruit Gemma与NeoPixel打造低成本声光互动架子鼓
  • AD21编译报错“contains floating input pins”?别慌,手把手教你修改元件库电气属性搞定它
  • 物联网轻量级通信协议AMTP-OpenClaw:为嵌入式设备打造高效通信桥梁
  • 模块六-数据合并与连接——36. 时间序列基础
  • AI三合一:微信团队颠覆性技术揭秘
  • 新手避坑指南:用EPSON RC+ 7.0虚拟机器人完成你的第一个项目(从安装到动起来)
  • 百度网盘解析工具实战指南:3分钟突破限速实现高速下载
  • Obsidian Excel插件:在知识管理系统中实现专业表格编辑与数据整合
  • 基于遗传算法的配电网故障重构研究【IEEE33节点】(Matlab代码实现)
  • 【独家首发】Midjourney针孔相机风格参数白皮书:基于1,842张生成图像的光学畸变量化分析(含f/1.4–f/16等效光圈映射表)
  • 智能科学与技术毕业设计题目怎么选
  • ElevenLabs希伯来文语音合成:从API调用失败到99.2%自然度达标的7步生产级优化流程
  • 基于CircuitPython与Adafruit IO的DIY智能门铃摄像头全栈开发指南
  • 如何用Photoshop图层批量导出工具提升3倍工作效率 [特殊字符]
  • WCH CH348L USB转多串口芯片实战:6路UART+2路RS485工业网关设计与电平兼容方案
  • 【负荷预测】基于LSTM-KAN的负荷预测研究(Python代码实现)