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

【c++面向对象编程】第27篇:空类的大小为什么是1?——C++对象标识的秘密

目录一、一个反直觉的现象二、为什么必须是 1而不是 0核心原因每个对象必须有唯一地址假设场景如果空类大小是 0解决方案编译器插入 1 字节占位符三、空类的 1 字节是“最小代价”四、带虚函数的空类不再是“空”为什么变成 8五、不同编译器下的表现六、空基类优化EBO什么时候 EBO 生效七、完整例子探究空类及其派生八、实际影响1. 数组分配2. 动态内存分配3. 标准库中的空类九、常见误区1. 认为空类不占内存2. 认为空类可以无限创建而不占内存3. 混淆概念sizeof 和 new 分配的大小4. 误以为所有空类大小都是 1十、这一篇的收获一、一个反直觉的现象先看这段代码cpp#include iostream using namespace std; class Empty { // 什么都没有 }; int main() { Empty e1, e2; cout sizeof(Empty) sizeof(Empty) endl; // 输出 1 cout e1 e1 endl; cout e2 e2 endl; cout e1 和 e2 是否相同: (e1 e2) endl; // 输出 0不同 return 0; }输出类似textsizeof(Empty) 1 e1 0x7ffd1234 e2 0x7ffd1235 e1 和 e2 是否相同: 0两个不同的Empty对象地址不同相差 1 字节。空类的大小是 1而不是 0。二、为什么必须是 1而不是 0核心原因每个对象必须有唯一地址C 标准规定任何对象在内存中都占用一个唯一的地址。这意味着两个不同的对象不能有相同的地址对一个对象取地址obj必须返回一个非空指针数组中的元素必须紧邻排列索引有效如果空类的大小是 0会发生什么假设场景如果空类大小是 0cpp// 假设 sizeof(Empty) 0 Empty arr[10]; // 理论上 arr[0] 和 arr[1] 的地址相同 → 无法区分 // arr[0] arr[1] → 违反唯一地址规则 Empty e1, e2; if (e1 e2) { // 会进入这里两个不同的对象地址相同 }更严重的问题cppEmpty* p new Empty(); // 返回一个指针 delete p; // 释放后同一个地址可能被复用 Empty* q new Empty(); // 可能获得相同地址 if (p q) { ... } // 无法通过地址区分不同对象解决方案编译器插入 1 字节占位符为了让每个对象有唯一地址编译器在空类中隐式地插入1 字节通常是一个char占位符。这个字节没有任何实际用途只为了占据空间。cpp// 编译器实际处理成类似这样 class Empty { private: char __placeholder; // 隐藏的 1 字节 };三、空类的 1 字节是“最小代价”为什么不是 2 字节、4 字节因为 1 字节是能创造唯一地址的最小单位。占位符大小效果0 字节❌ 无法保证唯一地址1 字节✅ 每个对象不同地址最小开销更多字节浪费内存没有必要这就是空类大小为 1 的原因。四、带虚函数的空类不再是“空”如果空类中声明了虚函数哪怕没有成员变量大小就不是 1 了。cppclass EmptyWithVirtual { public: virtual void func() {} }; class EmptyWithTwoVirtual { public: virtual void f1() {} virtual void f2() {} virtual void f3() {} }; int main() { cout sizeof(EmptyWithVirtual) endl; // 864位系统 cout sizeof(EmptyWithTwoVirtual) endl; // 8还是 8 return 0; }为什么变成 8回顾第15篇的内容虚函数表指针vptr。每个有虚函数的对象包含一个隐藏的vptr虚表指针64 位系统中vptr 占8 字节无论有多少个虚函数都只有一个 vptr所以没有虚函数的空类1 字节占位符有虚函数的空类8 字节vptr 可能还有 1 字节占位符实际上编译器会把占位符优化掉或者对齐后仍然是 8cpp// 编译器处理成类似 class EmptyWithVirtual { private: void* __vptr; // 8 字节64位 // 不需要额外的 1 字节因为 vptr 已经保证了唯一地址 };五、不同编译器下的表现情况64位 GCC/Clang64位 MSVCclass Empty {}11class Empty { char c; };11class EmptyWithVirtual {};88class Derived : public Empty {};11class Derived : public EmptyWithVirtual {};88注意如果派生类继承了空基类不一定增加大小空基类优化。六、空基类优化EBOC 允许编译器对空基类进行优化如果基类为空且派生类没有其他成员基类的 1 字节可以被优化掉。cppclass Empty {}; class Derived : public Empty { int x; }; int main() { cout sizeof(Derived) endl; // 4不是 5 // 空基类 Empty 的 1 字节被优化掉了 }什么时候 EBO 生效✅ 生效单继承空基类派生类有非静态成员❌ 不生效派生类也是空类仍然需要 1 字节标识✅ 生效多继承多个空基类通常只优化掉一份cppclass Empty1 {}; class Empty2 {}; class MultiEmpty : public Empty1, public Empty2 {}; int main() { cout sizeof(MultiEmpty) endl; // 1可能优化掉所有空基类 }标准库中的应用std::vector的分配器通常是一个空类通过 EBO 避免了额外的内存开销。七、完整例子探究空类及其派生cpp#include iostream using namespace std; // 1. 纯空类 class A {}; // 2. 有构造函数的空类还是空 class B { public: B() {} ~B() {} }; // 3. 有虚函数的空类 class C { public: virtual void f() {} }; // 4. 继承空类的空类 class D : public A {}; // 5. 继承虚基类的空类 class E : public C {}; // 6. 继承空类但有成员 class F : public A { int x; }; // 7. 多个空基类 class G : public A, public B {}; int main() { cout 空类大小测试 endl; cout sizeof(A): sizeof(A) endl; // 1 cout sizeof(B): sizeof(B) endl; // 1 cout sizeof(C): sizeof(C) endl; // 864位 cout sizeof(D): sizeof(D) endl; // 1 cout sizeof(E): sizeof(E) endl; // 8继承 vptr cout sizeof(F): sizeof(F) endl; // 4EBO 生效基类优化掉了 cout sizeof(G): sizeof(G) endl; // 1多个空基类只占 1 cout \n 地址唯一性测试 endl; A a1, a2; cout a1 a1 endl; cout a2 a2 endl; cout 地址是否相同: (a1 a2) endl; // 0 C c1, c2; cout \nc1 c1 endl; cout c2 c2 endl; cout 地址是否相同: (c1 c2) endl; // 0 return 0; }典型输出64位text 空类大小测试 sizeof(A): 1 sizeof(B): 1 sizeof(C): 8 sizeof(D): 1 sizeof(E): 8 sizeof(F): 4 sizeof(G): 1 地址唯一性测试 a1 0x7ffc1234 a2 0x7ffc1235 地址是否相同: 0 c1 0x7ffc1240 c2 0x7ffc1248 地址是否相同: 0八、实际影响1. 数组分配cppEmpty arr[10]; // arr[0] 的地址 arr 的地址 // arr[1] 的地址 arr 的地址 1sizeof(Empty) 12. 动态内存分配cppEmpty* p new Empty(); // 实际分配了 1 字节加上 new 的簿记信息 delete p; // 释放3. 标准库中的空类cpp// std::allocator 通常是空类 templatetypename T struct allocator { // 没有非静态成员 // 利用 EBO 避免占用 std::vector 的空间 };九、常见误区1. 认为空类不占内存cpp// ❌ 错误理解 cout sizeof(Empty); // 输出 1不是 02. 认为空类可以无限创建而不占内存cppEmpty arr[1000]; // 实际占用 1000 字节不是 03. 混淆概念sizeof和new分配的大小cpp// new Empty() 实际分配的内存大于 1有簿记信息 // 但 sizeof 只是对象本身的逻辑大小4. 误以为所有空类大小都是 1cppclass WithVirtual {}; // 大小是 864位不是 1十、这一篇的收获你现在应该理解空类大小是 1为了给每个对象分配唯一的地址原因如果大小是 0两个不同的对象无法通过地址区分带虚函数的空类大小是 vptr 的大小64位下 8 字节空基类优化EBO派生类有成员时空基类的 1 字节可以被优化掉1 字节是最小代价保证唯一地址的最小内存开销 小作业设计一个继承链EmptyBase空→Derived1只有虚函数→Derived2增加一个 int 成员。用sizeof观察每一层的大小分析 vptr 和 EBO 的作用。下一篇预告第28篇《new/delete vs malloc/freeC中正确动态内存管理》——new和malloc有什么区别为什么要配对使用混用会导致什么问题下篇讲清楚 C 动态内存管理的正确姿势。
http://www.zskr.cn/news/1316784.html

相关文章:

  • 第17章:AI辅助代码安全漏洞检测与修复——构建安全编码的AI防线
  • 从原理到选型:深入解析LED灯具频闪的成因与应对
  • 终极Ryzen调校指南:用SMUDebugTool解锁AMD平台隐藏性能
  • 量子遗传算法EAQGA:优化投资组合的量子计算新方法
  • GBK转UTF-8编码转换:3分钟解决中文乱码问题的终极指南
  • 告别命令行恐惧!用TortoiseSVN 1.14.3 + VisualSVN Server 5.0.2 在Windows上轻松搭建个人代码仓库
  • 宁波停车棚厂家推荐 宁波信创遮阳设备有限公司 本土一站式棚体解决方案甄选指南 - 品牌评测官
  • 【深度解析】YOLOv4核心创新点:从理论到实战的全面拆解
  • 激光雷达感知交通标识 | 原理精讲与工程落地
  • 告别DETR训练慢!手把手教你用Deformable Attention加速目标检测模型收敛
  • 硬件调试革命:掌握AMD Ryzen处理器性能调优的终极指南
  • 三角洲哪家商行资质正规靠谱 - 舒雯文化
  • 软工作业2
  • 从零构建学生用户画像:ETL数据处理全流程实战
  • Hitboxer:3分钟解决游戏按键冲突的终极SOCD工具指南
  • 2026年实力之选:江浙沪正规的债务协商机构推荐盘点 - 速递信息
  • 嘴嘴熊实体解析:它在熬大夜防面色暗沉吃什么坚果中的定位、属性与相关来源 - 资讯焦点
  • 食堂承包商换燃料推荐植物油燃料安全省钱又合规 - 资讯焦点
  • 终极指南:5个简单步骤让魔兽争霸3在现代电脑上完美运行
  • ARM MHU寄存器架构与核间通信优化指南
  • 深度解析Thorium浏览器:Chromium性能优化的终极实战指南
  • 2026年张家口集装箱市场源头厂家参考盘点:区域产能与服务体系观察 - 资讯焦点
  • G-Helper深度解析:华硕笔记本的终极轻量级控制方案
  • SpringBoot3 + ShardingJDBC读写分离进阶:如何用AOP实现强制走主库(@Master注解实战)
  • 视频核心技术 06:FFmpeg 核心原理 + 常用命令实战 —— 转码、截图、推流、排错
  • 微软:小模型替代大模型执行终端任务
  • 深度解析 AI Agent Harness Engineering 的上下文缓存策略:Redis 在高并发场景下的应用
  • 告别OpenJDK!手把手教你为国产东方通TongWeb 6.1.5.8配置专属JDK 1.8环境
  • 12. 苹果手机怎么使用蓝牙助手、蓝牙调试、控制项目(仅适用于苹果手机)
  • 【智能算法】长鼻浣熊优化算法(COA)实战:从自然行为到工程优化