C++ 中 struct 与 union 的区别:从内存共享到底层优化

发布时间:2026/7/28 19:58:02

C++ 中 struct 与 union 的区别:从内存共享到底层优化
C 中 struct 与 union 的区别从内存共享到底层优化一、引言看似相似的两种聚合类型struct和union是 C 中两种可以包含多个成员的复合类型。它们都能容纳不同类型的数据但在内存使用方式上有着本质差异。struct是我们日常编程中最常用的数据聚合方式而union则是一种特殊的内存复用机制。理解union的工作原理和适用场景对于底层系统编程、网络协议处理、嵌入式开发和内存优化都至关重要。C17 引入的std::variant更是为union提供了类型安全的现代替代方案。二、核心区别速览| 维度 | struct | union ||------|--------|-------|| 内存分配 | 所有成员各自拥有独立存储空间 | 所有成员共享同一块存储空间 || 大小 | 至少是所有成员大小之和(考虑对齐) | 等于最大成员的大小(考虑对齐) || 同时有效 | 所有成员同时有效| 同一时间只有一个成员有效|| 读写安全 | 读写任意成员皆安全 | 只能读写最后写入的那个成员 || 默认访问权限 | public | public || 构造/析构支持 | 完全支持 | 有限支持(有非平凡成员时需手动管理) || 典型应用 | 数据聚合、对象建模 | 类型双关、节省内存、状态机、协议解析 || C 现代替代 | — |std::variant(类型安全联合体) |三、内存布局的根本差异3.1 struct 的内存布局struct MyStruct { char a; // 1 字节 int b; // 4 字节 double c; // 8 字节 }; // sizeof(MyStruct) 可能是 16 或 24 (考虑内存对齐)总大小 16 字节所有成员同时存在struct 内存布局 (每个成员独立空间)a (1 字节)填充 3 字节b (4 字节)c (8 字节)关键点struct中所有成员同时存在各自占用独立的内存空间。你可以随时访问任意成员。3.2 union 的内存布局union MyUnion { char a; // 1 字节 int b; // 4 字节 double c; // 8 字节 }; // sizeof(MyUnion) 8 (等于最大成员 double 的大小)总大小 8 字节成员重叠在同一块内存上union 内存布局 (所有成员共享空间)最大成员 c 的空间: 8 字节a 占最低 1 字节b 占最低 4 字节c 占全部 8 字节同一时间只有一个有效关键点union中所有成员共享同一块内存大小等于最大成员的大小。同一时间只有一个成员有效。3.3 内存布局对比图union: 成员共享存储偏移 0char a / int b / double c全部从偏移 0 开始共享同一块内存总大小 max(各成员大小)struct: 各成员独立存储偏移 0: char a偏移 4: int b偏移 8: double c总大小 ≈ 成员大小之和(填充)四、union 的基本使用4.1 基本语法#include iostream union Data { int i; double d; char c; }; int main() { Data data; // 写入 int data.i 42; std::cout data.i std::endl; // 42 // 注意此时 data.d 和 data.c 的值是未定义的 // 写入 double (覆盖之前的 int) data.d 3.14159; std::cout data.d std::endl; // 3.14159 // 注意此时 data.i 的值是未定义的 // 写入 char data.c A; std::cout data.c std::endl; // A return 0; }4.2 匿名 unionstruct Packet { int type; // 匿名 union成员直接访问 union { struct { int x, y; } point; // type 1 struct { int width, height; } rect; // type 2 char text[16]; // type 3 }; // 无需通过 union 变量名直接访问内部成员 }; int main() { Packet p; p.type 1; p.point.x 10; p.point.y 20; // 直接访问没有额外的 union 成员名 p.type 3; strcpy(p.text, Hello); }五、union 进行内存优化的典型场景5.1 场景一节省内存的状态机#include iostream #include string // 不使用 union每个状态都占用空间 struct NaiveState { int stateType; // 0: int, 1: double, 2: string int intValue; // 浪费空间只在 stateType0 时使用 double doubleValue; // 浪费空间只在 stateType1 时使用 std::string stringValue; // 浪费空间只在 stateType2 时使用 }; // sizeof(NaiveState) 非常大包含全部三种数据 // 使用 union只占最大成员 状态标记 union StateData { int intValue; double doubleValue; std::string stringValue; StateData() : intValue(0) { } // 默认初始化为 int ~StateData() { } // 析构函数需手动管理 }; struct OptimizedState { int stateType; // 0: int, 1: double, 2: string StateData data; }; // sizeof(OptimizedState) 约等于 int sizeof(StateData) // StateData 大小 max(sizeof(int), sizeof(double), sizeof(string))5.2 场景二类型双关(Type Punning) — 底层位操作#include cstring #include iostream // 将浮点数按位解释为整数 union FloatInt { float f; uint32_t i; }; int main() { FloatInt converter; converter.f 3.14159f; // 查看浮点数的 IEEE 754 内部表示 std::cout Float: converter.f std::endl; std::cout Hex: 0x std::hex converter.i std::endl; // 修改符号位 converter.i ^ 0x80000000; std::cout Negated: converter.f std::endl; // -3.14159 return 0; }注意在 C 中通过union进行类型双关是合法的(与 C 不同C 中技术上属于未定义行为但几乎所有编译器都支持)但读取非最后写入的成员在不同标准版本中有不同规定。C20 起这种用法更加明确。5.3 场景三网络协议解析#include cstdint #include cstring #include iostream // 以太网帧头 union EthernetHeader { struct { uint8_t destMac[6]; uint8_t srcMac[6]; uint16_t etherType; } fields; uint8_t raw[14]; }; // IP 地址的多种表示 union IPAddress { uint32_t asInt; uint8_t asBytes[4]; struct { uint8_t b1, b2, b3, b4; } octets; }; int main() { // 从网络接收的原始字节 uint8_t rawPacket[] { 0x00, 0x1A, 0x2B, 0x3C, 0x4D, 0x5E, // 目标 MAC 0x00, 0x6F, 0x7E, 0x8D, 0x9C, 0xAB, // 源 MAC 0x08, 0x00 // 以太类型(IPv4) }; EthernetHeader header; std::memcpy(header.raw, rawPacket, 14); std::cout EtherType: 0x std::hex header.fields.etherType std::endl; IPAddress ip; ip.octets {192, 168, 1, 100}; std::cout IP as int: std::dec ip.asInt std::endl; // 注意字节序小端机器上输出可能与直觉不同 return 0; }5.4 场景四变体类型(Variant Type) — 动态类型#include iostream #include string #include stdexcept // 一个可以存储不同类型值的变体 enum class ValueType { INT, DOUBLE, STRING, NONE }; class Variant { ValueType type ValueType::NONE; union Storage { int intVal; double doubleVal; std::string stringVal; Storage() : intVal(0) { } ~Storage() { } } storage; public: Variant() default; ~Variant() { if (type ValueType::STRING) { storage.stringVal.~basic_string(); } } void setInt(int val) { if (type ValueType::STRING) { storage.stringVal.~basic_string(); } storage.intVal val; type ValueType::INT; } void setDouble(double val) { if (type ValueType::STRING) { storage.stringVal.~basic_string(); } storage.doubleVal val; type ValueType::DOUBLE; } void setString(const std::string val) { if (type ValueType::STRING) { storage.stringVal val; } else { new (storage.stringVal) std::string(val); // placement new type ValueType::STRING; } } ValueType getType() const { return type; } int getInt() const { if (type ! ValueType::INT) throw std::runtime_error(Not an int); return storage.intVal; } // ... getDouble, getString 类似 }; int main() { Variant v; v.setInt(42); std::cout v.getInt() std::endl; v.setString(Hello); // v 现在存储字符串int 数据被覆盖 v.setDouble(3.14); // v 现在存储 double字符串被正确析构 }重要这个手动管理的union变体存在大量陷阱。在现代 C 中应优先使用std::variant。六、union 的限制与陷阱6.1 非平凡类型的成员需要手动管理union Problematic { int x; std::string s; // std::string 有非平凡构造/析构 // C11 起编译器不会自动生成构造/析构函数 // 必须手动提供 Problematic() : x(0) { } ~Problematic() { } }; int main() { Problematic p; // 需要使用 placement new 来构造 string new (p.s) std::string(Hello); // 使用完毕后需要手动析构 p.s.~basic_string(); }6.2 读取非活跃成员union Data { int i; float f; }; Data d; d.i 42; // float x d.f; // 技术上这是读取非活跃成员 // C20 起对某些类型放宽了限制在 C 中读取 union 中非最后写入的成员行为是未定义的(C 语言中是合法的类型双关但 C 不同)。如果需要进行类型双关C20 起可以使用std::bit_cast。6.3 union 不能包含引用类型成员union BadUnion { int x; // int ref; // 错误union 不能包含引用类型成员 };6.4 union 不能作为基类或被继承// union Base { }; // 错误union 不能作为基类 // class Derived : Base { }; // 错误不能从 union 继承七、现代 C 的替代方案std::variantstd::variant(C17)提供了类型安全的联合体替代#include variant #include iostream #include string using Variant std::variantint, double, std::string; int main() { Variant v 42; // 存储 int v 3.14159; // 存储 double v std::string(Hello); // 存储 string // 安全的访问方式 if (std::holds_alternativestd::string(v)) { std::cout std::getstd::string(v) std::endl; } // 使用 std::visit 进行类型安全的访问 std::visit([](const auto value) { using T std::decay_tdecltype(value); if constexpr (std::is_same_vT, int) { std::cout int: value std::endl; } else if constexpr (std::is_same_vT, double) { std::cout double: value std::endl; } else if constexpr (std::is_same_vT, std::string) { std::cout string: value std::endl; } }, v); return 0; }std::variant 的优势| 特性 | union | std::variant ||------|-------|--------------|| 类型安全 | 无(手动跟踪类型) | 是(自动跟踪编译期检查) || 析构管理 | 手动(placement new/delete) | 自动(RAII) || 非平凡类型支持 | 需手动管理 | 自动处理 || 错误访问 | 未定义行为 | 抛出 std::bad_variant_access || 访问方式 | 直接读取(危险) | std::get / std::visit || 内存开销 | 等于最大成员 | 最大成员 类型索引(通常 4-8 字节) |八、总结struct和union代表了两种截然不同的内存组织方式struct — 成员共存所有成员同时存在各自占有独立空间。适用于需要在对象中同时保持多个属性的场景是面向对象编程和数据结构的基础。union — 成员互斥所有成员共享同一块内存同一时间只有一个成员有效。核心价值在于内存复用和底层数据表示操作。union 的优化场景节省内存当多个数据字段不会同时使用(如状态机、变体类型)时用 union 显著减少内存占用类型双关在不改变二进制表示的前提下以不同数据类型查看同一段内存(如浮点数与整数的位操作)协议解析将网络数据包或文件格式的原始字节与结构化字段映射到同一内存区域底层硬件操作在嵌入式系统中将硬件寄存器映射为不同位字段的组合现代 C 建议union是底层工具在使用非平凡类型(如std::string)时需要手动管理生命周期(placement new 和显式析构)极易出错。C17 引入的std::variant提供了类型安全的联合体替代方案在大多数应用场景中应优先使用。只有在纯粹的位操作、C 兼容性和极致性能要求的底层代码中union仍然是不可替代的工具。

相关新闻

解决无法将“babel”项识别为 cmdlet、函数、脚本文件或可运行程序的名称。

解决无法将“babel”项识别为 cmdlet、函数、脚本文件或可运行程序的名称。

2026/7/28 19:58:02

最近在做node.js 前端的时候,遇到一个问题,全局安装了Babel,Babel提供babel-cli工具,用于命令行转码 但在查看是否安装成功,输入:babel --version 命令报错: 无法将“babel”项识别为 cmdlet、函…

热搜上的“全款买房”潮,正在重塑深圳光明的价值逻辑

热搜上的“全款买房”潮,正在重塑深圳光明的价值逻辑

2026/7/28 19:58:02

最近,“全款买房的人在变多”冲上热搜。据深圳贝壳研究院监测,2026年上半年深圳二手房全款买家成交占比已达25.3%,相当于每四个二手房买家当中,就有一个选择一次性付清。这放在十年前几乎不可想象。“能贷七成绝不贷五成”“房贷是…

Opus 5模型在Conductor平台的高性价比AI应用实践指南

Opus 5模型在Conductor平台的高性价比AI应用实践指南

2026/7/28 19:58:02

在AI模型快速迭代的今天,开发者和技术团队常常面临一个现实难题:如何在有限的预算内获得接近顶级模型的性能表现?近期,Opus 5模型正式登陆Conductor平台,以其接近Fable模型的性能表现和仅一半的价格优势,为…

幻兽帕鲁存档编辑终极指南:如何安全修改游戏数据而不损坏存档

幻兽帕鲁存档编辑终极指南:如何安全修改游戏数据而不损坏存档

2026/7/28 20:58:05

幻兽帕鲁存档编辑终极指南:如何安全修改游戏数据而不损坏存档 【免费下载链接】palworld-save-tools Tools for converting Palworld .sav files to JSON and back 项目地址: https://gitcode.com/gh_mirrors/pa/palworld-save-tools 你是否曾经因为游戏存档…

学术论文写作工具选择指南与高性价比推荐

学术论文写作工具选择指南与高性价比推荐

2026/7/28 20:58:05

1. 论文写作工具的选择困境作为一名在学术圈摸爬滚打多年的研究者,我深知论文写作过程中工具选择的重要性。记得刚开始读研时,我花了大价钱购买了一套所谓的"专业级"文献管理软件,结果发现功能复杂难用,最后反而拖慢了研…

vJoy虚拟摇杆驱动:Windows游戏开发者的终极输入解决方案

vJoy虚拟摇杆驱动:Windows游戏开发者的终极输入解决方案

2026/7/28 20:58:05

vJoy虚拟摇杆驱动:Windows游戏开发者的终极输入解决方案 【免费下载链接】vJoy Virtual Joystick 项目地址: https://gitcode.com/gh_mirrors/vj/vJoy 你是否曾遇到过这样的困境:想要测试游戏手柄兼容性却没有物理设备?或者需要将键盘…

公链节点维护成本解析与优化策略

公链节点维护成本解析与优化策略

2026/7/28 20:58:05

1. 公链节点维护费的本质解析公链节点的年维护费用绝非简单的服务器租赁成本,而是区块链网络维持去中心化特性的经济门槛。以以太坊为例,一个全节点每年基础硬件成本约$3,000-$5,000,这包含:企业级SSD(至少2TB NVMe&am…

提示词驱动的AI Agent:CLI-Anything技术解析

提示词驱动的AI Agent:CLI-Anything技术解析

2026/7/28 20:58:05

1. CLI-Anything 的本质:提示词驱动的AI Agent 当我第一次看到CLI-Anything这个项目时,最让我震惊的是它完全颠覆了传统命令行工具的开发模式。作为一个长期从事CLI工具开发的工程师,我习惯性地去GitHub仓库里寻找核心引擎代码,结…

深圳花园婚礼场地趋势与黄金评估体系

深圳花园婚礼场地趋势与黄金评估体系

2026/7/28 20:48:04

1. 2026年深圳花园婚礼场地趋势前瞻 作为在深圳婚庆行业深耕十年的策划师,我见证了这座城市户外婚礼场地的迭代升级。2026年的深圳花园婚礼场地将呈现三大特征:生态化设计成为标配(90%新场地配备垂直绿化系统)、智能化设备全覆盖&…

[具身智能-649]:个人电脑搭建 RTSP 服务完整方案(Windows / Ubuntu 双平台,适配 RDK X5 rtsp2display 调试)

[具身智能-649]:个人电脑搭建 RTSP 服务完整方案(Windows / Ubuntu 双平台,适配 RDK X5 rtsp2display 调试)

2026/7/28 13:30:18

目标:电脑作为RTSP 服务端,循环推送 H264/H265 视频流; RDK X5 通过 rtsp2display 拉流预览,完全不需要在开发板编译 live555。 提供两套成熟方案: ✅ 方案 A:FFmpeg(最简单,优先推…

PDF合并与动态水印的工程化方案:2026国内免费工具实测对比

PDF合并与动态水印的工程化方案:2026国内免费工具实测对比

2026/7/28 16:04:36

一、背景与测试方案 在实际项目交付中,PDF文件合并与版权保护水印的叠加是一个高频但容易被低估的技术需求。典型的处理链路涉及:多源PDF的文件流合并、页面级水印渲染(含透明度混合与图层叠加)、输出文件体积控制。看似简单的操作…

PDF拆分压完图糊了?2026国内免费实测,档案员都在用的组合方案

PDF拆分压完图糊了?2026国内免费实测,档案员都在用的组合方案

2026/7/28 16:04:35

说实话,提到PDF拆分再压缩,我真是被折腾得够呛。 上个月公司年度合同归档,一份300多页的PDF总合同,需要按年份拆分成三个独立文件,再分别压缩到10MB以内方便邮件发送各部门确认。我心想这还不简单?先找个海…

零基础搭建桌面智能体,OpenClaw 2.7.9 分步实操,避开绝大多数部署陷阱

零基础搭建桌面智能体,OpenClaw 2.7.9 分步实操,避开绝大多数部署陷阱

2026/7/28 0:06:55

📌 一、工具核心优势盘点 数据本地存储,安全系数高所有操作日志、文档资料均保存在本机,不会上传至云端,能够有效保护企业文件与个人隐私,规避数据泄露风险。 上手简单,零编程门槛采用全图形化可视化界面&…

计算机毕业设计之基于springboot的购物平台设计与实现

计算机毕业设计之基于springboot的购物平台设计与实现

2026/7/28 0:06:55

由于移动应用技术的持续性的快速发展,现实生活中人们大多数都是通过移动手机、电脑等智能设备来完成生活中的事务。因此,许多的人工传统行业也开始与互联网结合,不再一味的依靠人工手动,努力打造半自动数字化甚至是全自动数字化模…

豆包AI绘图提示词失效真相:NLP模型层token截断机制首次披露,3招绕过字数限制

豆包AI绘图提示词失效真相:NLP模型层token截断机制首次披露,3招绕过字数限制

2026/7/28 0:06:55

更多请点击: https://codechina.net 第一章:豆包AI绘图提示词失效现象全景扫描 近期大量用户反馈,豆包(Doubao)AI绘图功能对常规提示词(Prompt)响应异常:语义明确的指令被忽略、中英…