news 2026/5/17 0:42:22

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

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
【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位系统):

text

sizeof(Empty): 1 sizeof(Data): 16 sizeof(WithFunc): 4

关键观察

  • 空类占用1字节(C++保证每个对象有唯一地址)

  • WithFunc的大小是4字节,就是int x的大小——成员函数不占用对象内存!


二、成员变量的存储

非静态成员变量

每个对象都有自己独立的一份非静态成员变量:

cpp

class 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的地址(不同) }

内存布局

text

p1 对象: [ x (4字节) ][ y (4字节) ] p2 对象: [ x (4字节) ][ y (4字节) ] 静态成员 count: 存储在全局数据区(所有对象共享)

静态成员变量

静态成员变量不存储在对象中,而是存储在全局/静态数据区(类似全局变量)。所有对象共享同一份。


三、成员函数的存储

非虚成员函数

成员函数的代码存储在代码区(text segment),所有对象共享。编译器在编译时将成员函数转换成普通函数,隐式传递this指针。

cpp

class 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位系统)。

cpp

class 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高效访问而做的优化。对象大小不一定等于成员变量大小的简单相加

对齐规则(简化版)

  1. 每个类型有自己的对齐要求(通常是其大小的倍数)

  2. 结构体的总大小是其最大成员对齐要求的整数倍

  3. 成员按声明顺序排列,但可能插入填充字节

示例:同样的成员,不同顺序,大小不同

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的倍数 总大小: 12

B的布局

text

偏移0-3: int i (4字节) 偏移4-5: short s (2字节) 偏移6: char c (1字节) 偏移7: 填充 (1字节) ← 让整个结构体是4的倍数 总大小: 8

教训:把大的成员放在前面,小的放在后面,通常能减小对象大小。


五、对象大小计算规则总结

组成部分是否占用对象内存说明
非静态成员变量✅ 是每个对象独立存储
静态成员变量❌ 否存储在静态区,对象共享
成员函数(非虚)❌ 否代码存储在代码区
虚函数表指针vptr✅ 是如果类有虚函数,每个对象多一个vptr
内存对齐填充✅ 是为了对齐而插入的空白字节

计算对象大小的步骤

  1. 累加所有非静态成员变量的大小

  2. 如果类有虚函数,加上vptr大小(64位系统8字节)

  3. 考虑内存对齐,总大小调整为最大成员对齐的整数倍

cpp

class 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); // 16(64位)

六、完整例子:观察内存布局

cpp

#include <iostream> using namespace std; // 辅助函数:打印对象内存的十六进制表示 void printMemory(const void* obj, size_t size) { const unsigned char* bytes = static_cast<const 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. 忽略对齐导致的大小计算错误

cpp

struct Misaligned { char a; double b; // 需要8字节对齐 }; // 实际大小是16(a+7填充+b),不是9

3. 认为空类不占内存

cpp

Empty e1, e2; cout << (&e1 == &e2); // false,不同的对象必须有不同地址 // 所以空类占1字节(或更多,取决于编译器)

4. 混淆静态成员变量和全局变量

静态成员变量存储在静态区,但作用域在类内。


八、这一篇的收获

你现在应该理解:

  • 成员函数不占对象内存:函数代码所有对象共享,通过this区分

  • 对象大小由非静态成员变量决定:加上vptr(如果有虚函数)和对齐填充

  • 内存对齐:CPU访问效率的优化,可能导致对象比成员总和大

  • 布局优化:把大成员放前面可以减小对象大小

  • 静态成员:不属于对象,存储在全局/静态区

💡 小作业:定义几个结构体,包含charshortintdouble类型成员,用不同顺序排列,sizeof观察大小变化。设计一个“最优”布局和最差布局,验证对齐规则。


下一篇预告:第27篇《空类的大小为什么是1?——C++对象标识的秘密》——深入探讨空类占1字节的原因,以及带虚函数的空类为什么是8(64位)?理解对象标识和唯一地址的设计哲学。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/5/17 0:40:18

告别3D-DNA的卡顿:用Chromap+Yahs快速搞定植物Hi-C辅助组装(附完整代码)

植物基因组Hi-C辅助组装新方案&#xff1a;ChromapYahs全流程解析 在植物基因组研究中&#xff0c;Hi-C技术已成为提升组装连续性的重要手段。然而传统3D-DNA流程在植物数据上的表现常令研究者头疼——运行速度缓慢、内存占用高&#xff0c;且对植物特有的重复序列处理效果欠佳…

作者头像 李华
网站建设 2026/5/17 0:38:18

儿童语音合成不能只靠“可爱”!ElevenLabs底层音素建模缺陷与3种年龄适配性补偿方案,一线教育科技团队内部流出

更多请点击&#xff1a; https://intelliparadigm.com 第一章&#xff1a;儿童语音合成不能只靠“可爱”&#xff01;ElevenLabs底层音素建模缺陷与3种年龄适配性补偿方案&#xff0c;一线教育科技团队内部流出 ElevenLabs 的 TTS 模型虽在成人语音自然度上表现优异&#xff0…

作者头像 李华
网站建设 2026/5/17 0:37:48

Unity UI锚点(Anchors)全解析:从原理到实战自适应布局

1. 锚点系统的基础认知 第一次打开Unity的UI系统时&#xff0c;那个带着四个小三角的蓝色矩形框绝对让人印象深刻。这四个不起眼的三角标记&#xff0c;实际上掌控着UI元素在不同屏幕尺寸下的命运。想象你正在设计一个手游界面&#xff0c;在1080p的测试机上完美运行的按钮&…

作者头像 李华
网站建设 2026/5/17 0:37:40

Stable Diffusion 2.1 Base终极实战指南:从零掌握AI绘画核心技术

Stable Diffusion 2.1 Base终极实战指南&#xff1a;从零掌握AI绘画核心技术 【免费下载链接】stable-diffusion-2-1-base 项目地址: https://ai.gitcode.com/hf_mirrors/ai-gitcode/stable-diffusion-2-1-base 还在为AI绘画效果不理想而烦恼吗&#xff1f;Stable Diff…

作者头像 李华
网站建设 2026/5/17 0:36:09

人大金仓KingbaseES ksql元命令实战:从数据库探秘到运维提效

1. 初识KingbaseES ksql&#xff1a;数据库管理的瑞士军刀 第一次接触人大金仓KingbaseES的ksql工具时&#xff0c;我完全被它的强大功能震撼到了。作为一名常年与数据库打交道的开发者&#xff0c;我发现ksql远不止是一个简单的命令行客户端&#xff0c;而是一个集成了数据库操…

作者头像 李华
网站建设 2026/5/17 0:27:58

K210实战:三种高效部署kmodel模型至TF卡的进阶方案

1. K210模型部署的痛点与进阶方案概览 第一次用K210做图像识别项目时&#xff0c;最让我头疼的就是模型部署问题。每次修改模型都要反复插拔TF卡&#xff0c;调试过程像在玩打地鼠游戏。后来才发现&#xff0c;基础的拷贝粘贴只是入门操作&#xff0c;真正高效的部署方式能节省…

作者头像 李华