共用体与大小端实战
掌握 union 共享内存机制与大小端判断,学会 int 与字节数组的安全互转(网络字节序前置)
- 来源
- 补充
标记说明:【来源】来自上传资料 · 【补充】课程新编 · 【纠错】按勘误表修正 · 【更新】过时内容已现代化 · 【待确认】无法可靠还原
完成标准(本章)
- 📖 已阅读:滚动 ≥ 80% 且有效阅读 ≥ 180 秒
- ✏️ 已练习:小练习正确率 ≥ 60%
- 📝 已通过测验:分数 ≥ 60 分
- 🛠️ 已掌握还需完成实践任务
1.3.7 共用体与大小端实战
本章来源:union 定义与共享内存、与 struct 的区别、大小端概念与判断来自《第一阶段讲义》22.x + 13.5 节【来源】,经重新组织表述;union 类型双关(type punning)的可移植性边界与 memcpy 安全替代为资料未覆盖的新编【补充】。平台适用性 universal——但大小端判断结果随平台不同,本章输出为本机(x86_64 小端)实测。
① 学习目标
- 说出 union 与 struct 的本质区别(成员共享同一块内存 vs 成员各占各的);
- 解释大小端概念,并写出"指针法/共用体法"判断本机字节序的程序;
- 说明"判断字节序"程序的实现定义性质——工程上常见,但不把某次输出当语言保证;
- 用 memcpy 实现 int 与 4 字节数组的安全互转(避免 union 类型双关的可移植性争议);
- 理解字节序与网络字节序的衔接(6.1.2 前置)。
② 前置知识
- 必选:1.3.6 结构体与位段(union 语法与 struct 同源;
->/.访问一致)。
③ 核心概念【来源】
| 概念 | 说明 |
|---|---|
| union 定义与共享内存 | union Word { unsigned int u; unsigned char b[4]; };——所有成员从同一地址开始,union 大小 = 最大成员的大小;任一时刻只应有一个成员是"当前有效"的 |
| union vs struct | struct 成员同时存在(各占内存);union 成员互斥共享(省内存,同一块内存按不同视角解读) |
| 大小端 | 多字节整数在内存中的字节排列:小端=低地址存低位字节(x86/ARM 默认);大端=低地址存高位字节(网络字节序) |
| 判断方法 | 存 0x11223344 后读首字节:0x44 → 小端;0x11 → 大端(指针法读首字节 / union 法均可) |
| union 类型双关 | 写 u 后读 b 属于"读非最后写入的成员"——C11 定义为实现定义行为(工程上极常见,但严格可移植性存疑);memcpy 逐字节复制是标准保证的安全替代 |
| int ↔ 字节数组 | 网络收发、协议打包必须把数值拆成字节序列:memcpy(bytes, &v, 4) / memcpy(&v, bytes, 4) 得到的是本机字节序的字节序列——跨机器传输前还要做网络字节序转换(6.1.2) |
| 字节序 vs 位序 | 字节序=多字节值中字节的排列;位序=单字节内位的排列(位段/串口协议关注,1.3.6 已述实现定义) |
④ 通俗解释【补充】
- union 是"一房多租":同一间屋子,你按"unsigned int"看它是 4 字节数,按"char[4]"看它是 4 个字节——同一时刻只住一个人,谁最后入住(最后写入)谁说了算;
- struct 是"套房":每个成员一间房,各住各的;
- 大小端是"写字方向":数字 0x11223344 写成一排字节,小端从右边(低位)开始写(低地址放 0x44),大端从左边(高位)开始写(低地址放 0x11)——数值本身不变,变的是"存在内存里的排列";
- union 判断大小端像"偷看门牌":把数字塞进屋子,再从门缝看第一格挂的是什么——看到 0x44 说明本机从低位开始排(小端)。这招工程上人人用,但严格说它依赖实现定义,所以本章同时教memcpy 正规搬家公司的走法(字节序列显式可见);
- 网络通信必须"统一写字方向":两台机器可能一个从左写、一个从右写,直接发原始字节对方会读成另一个数——所以协议统一规定网络字节序(大端),收发双方用 htonl/ntohl 转换(6.1.2)。
⑤ 示例代码
代码示例与验证记录
- examples/ex1-union-endian.cunion 共享内存演示 + 大小端判断(本机自检)✓ 已实测(gcc 15.2.0 / MinGW-w64 x86_64 / Windows 11, 2026-08-16)编译:
gcc ex1-union-endian.c -o ex1 -std=c11 -Wall -Wextra -Wpedantic适用环境:LinuxWindows(MinGW)展开预期输出(实测)
sizeof(union Word)=4 sizeof(unsigned int)=4(union 大小=最大成员) u = 0x11223344 b[0]=0x44 b[1]=0x33 b[2]=0x22 b[3]=0x11 本机为小端序(低地址存低位字节)
差异说明:本机 x86_64 MinGW gcc 15.2.0 实测(0 警告);写 u 后读 b 是实现定义行为(自检用途);换大端平台最后一行输出相反
完整源码见 /code 代码示例页
- examples/ex2-memcpy-convert.cint 与 4 字节数组互转(memcpy 安全版)✓ 已实测(gcc 15.2.0 / MinGW-w64 x86_64 / Windows 11, 2026-08-16)编译:
gcc ex2-memcpy-convert.c -o ex2 -std=c11 -Wall -Wextra -Wpedantic适用环境:LinuxWindows(MinGW)展开预期输出(实测)
v=0x01020304 bytes[0..3]=04 03 02 01(本机字节序的字节序列) back=0x01020304(往返一致: yes)
差异说明:本机实测(0 警告);字节序列为本机(小端)字节序,跨机器传输前需 htonl/ntohl 网络字节序转换(6.1.2)
完整源码见 /code 代码示例页
- examples/ex3-union-punning-portability.txtunion 类型双关的可移植性边界(conceptual:静态讲解,不编译不运行)概念讲解示例✓ 文档核对(非执行)· C11 标准 6.5.2.3/6.5 条文核对, 2026-08-16编译:
无(静态讲解文本)适用环境:LinuxWindows(MinGW)展开文档核对记录
(无运行输出——本示例为静态讲解文本,见文件内容)
差异说明:内容按 C11 6.5.2.3 注 95(union 成员访问)与 6.5 p6/p7(严格别名)核对
完整源码见 /code 代码示例页
⑥ 编译与运行方法
全部示例为本机实测(x86_64 MinGW gcc 15.2.0 / Windows 11,-std=c11 -Wall -Wextra -Wpedantic 编译 0 警告):
gcc ex1-union-endian.c -o ex1 -std=c11 -Wall -Wextra -Wpedantic
gcc ex2-memcpy-convert.c -o ex2 -std=c11 -Wall -Wextra -Wpedantic- ex1 本机输出
b[0]=0x44→ 本机为小端序(x86_64 实测;换大端平台输出相反); - ex2 的 memcpy 互转往返一致(yes),字节序列为本机字节序——跨机器传输前需网络字节序转换(6.1.2)。
⑦ 常见错误
| 症状 | 原因 | 解决 |
|---|---|---|
| 用 union 读写不同成员,换平台结果不同 | 读非最后写入成员是实现定义行为(C11 6.5.2.3 注 95) | 需要严格可移植时用 memcpy 逐字节互转(ex2);union 读法仅用于"本平台自检/已知平台"场景 |
| 把字节序判断程序的结果写死进业务代码 | 大小端是平台属性,不是语言保证 | 运行时判断或编译期宏(如 __BYTE_ORDER__,实现定义)——不要假设"所有板子都小端" |
| 直接发送 int 的原始字节到另一台机器 | 两端字节序可能不同 | 传输前 htonl/ntohl 转网络字节序(6.1.2 展开) |
| 以为 union 能同时存多个成员 | union 成员共享内存,存新的覆盖旧的 | 任一时刻只读"最后写入"的成员;需要多值并存用 struct |
用 union { int; char* } 存指针再当整数用 | 指针与整数大小/表示可能不同 | 不要跨"无关类型"复用 union;类型双关只用于字节级重解释 |
⑧ 小练习
小练习
学习自测:提交后才显示答案与解析(前端判分,不作为正式考试)ex-1-3-7-1.union Word { unsigned int u; unsigned char b[4]; }; 与 struct 相比,union 的本质区别是?(单选)
◌ 未作答ex-1-3-7-2.本机存 0x11223344 后读首字节得 0x44,说明?(单选)
◌ 未作答ex-1-3-7-3.代码审查:同事用 *(unsigned int*)&float_val 把 float 当整数读位模式。这个写法的问题主要是?(单选)
◌ 未作答
⑨ 章节测验
章节测验
⑩ 实战任务
实践任务
大小端判断与 int 字节数组互转
写程序完成两件事:① 判断本机字节序并输出结果与依据(union 法或指针法,注明实现定义性质); ② 用 memcpy 实现 int 与 4 字节数组互转函数并做往返一致性验证; 最后写一段 100 字以上的说明,讲清"为什么网络传输前要做字节序转换"(交叉引用 6.1.2 网络字节序)。
输入与输出
无程序输入。交付物:① 字节序判断程序输出(本机为小端,依据为首字节 0x44); ② to_bytes/from_bytes 互转与往返验证输出;③ 字节序转换必要性的说明文字。
功能要求
- 字节序判断:存 0x11223344 读首字节(union 法或指针法均可)
- 互转函数用 memcpy 实现(不用指针强转)
- 往返验证:任意数值 from_bytes(to_bytes(v)) == v
- 说明文字提到"网络字节序(大端)"与"htonl/ntohl"(6.1.2 预告)
限制条件
- 判断程序输出前注明"本机实测,union 读法为实现定义行为"
- 不把判断结果写死进业务逻辑(演示程序除外)
验收步骤(自检清单 0/3)
验收标准
- 判断输出为小端且依据正确(首字节 0x44,验收步骤 1)
- 往返验证输出 yes(验收步骤 2)
- 说明提到网络字节序与 htonl/ntohl(验收步骤 3)
常见失败原因
- 用指针强转做互转(严格别名问题)
- 把本机字节序结果当成所有平台的结果写进注释
- 说明里没提网络字节序转换
可选扩展
- 用 float 的位模式查看(memcpy 到 unsigned int)打印 IEEE754 十六进制
- 交叉实现:手写一个 16 位值的字节交换(bswap)函数
完成必要清单后才能计入"已完成实践"(学习状态自动推导,不提供一键完成)
⑪ 面试问题
面试问题
union 和 struct 有什么区别?union 适合哪些场景?高频C/C++ · easy
要点:struct 成员各占内存、同时有效;union 成员共享同一块内存(大小=最大成员)、任一时刻只有一个成员有效。union 适合寄存器双视角、字节序判断、协议字段多形态解析等字节级重解释场景。
struct:成员顺序存放、各自独立,sizeof 为各成员之和(加对齐填充),所有成员同时有效。 union:所有成员从同一地址开始共享内存,sizeof 等于最大成员的大小(含对齐), 任一时刻至多一个成员有值(读非最后写入成员是实现定义行为)。 典型场景:① 硬件寄存器以"整体 32 位 + 分字段"双视角访问;② 字节序自检 (写整型读字节数组);③ 协议包"同一段字节按不同类型解析"。 注意可移植性:跨平台严格代码用 memcpy 逐字节替代(见本章 ex2/ex3)。
追问:- 追问:为什么说 union 是省内存而不是多存数据?(共享而非叠加)
评分要点:- 存储模型区别(叠加 vs 共享)
- 至少一个典型场景
- 实现定义/可移植性提示
什么是大小端?怎么用 C 判断本机字节序?高频C/C++ · medium
要点:大小端是多字节整数在内存中的字节排列方向;写 0x11223344 后读首字节(0x44 小端 / 0x11 大端)即可判断。
大小端(endianness):大端=低地址存高位字节("从左到右",网络字节序);小端=低地址 存低位字节("从右到左",x86/ARM 默认)。判断:union { unsigned int u; unsigned char b[4]; } 或指针法——存 0x11223344 后看首字节是 0x44(小端)还是 0x11(大端)。 必须说清边界:读非最后写入的 union 成员是实现定义行为(自检用途可接受); 且字节序是平台属性,代码不应把"所有板子都小端"当假设——网络传输一律 htonl/ntohl 显式转换。
追问:- 追问:网络字节序是大端还是小端?(大端)
评分要点:- 大小端定义准确
- 判断代码思路
- 实现定义与网络字节序提示
int 与字节数组互转,为什么推荐 memcpy 而不是直接指针强转或 union 双关?高频C/C++ · medium
要点:指针强转违反严格别名规则,union 双关是实现定义行为;memcpy 逐字节复制是标准保证的,且编译器会优化回等价高效代码。
三种写法功能相同(把 int 的字节序列拆出来/拼回去),可移植性不同: ① 指针强转(*(unsigned char*)&v)——类型不同的左值访问对象,涉及严格别名规则 (C11 6.5 p6/p7),严格可移植性无保证;② union 双关——标准明确允许 union 成员访问 作为别名例外,但读非最后写入成员属实现定义行为(6.5.2.3 注 95),且受字节序影响; ③ memcpy——把 v 的字节逐字节复制进数组,标准保证行为,现代编译器能把它优化成 与直接访问等价的高效代码(没有性能损失)。因此"安全 + 不损失性能"的默认选择是 memcpy, 再叠加显式字节序转换(htonl/ntohl)完成跨机传输。
追问:- 追问:memcpy 之后字节数组里是什么顺序?(本机字节序;跨机传输前转网络字节序)
评分要点:- 三种写法的可移植性差异
- 严格别名与实现定义两个标准依据
- 优化无损失的认识
⑫ 延伸阅读
- 《C 语言程序设计:现代方法》第 16 章 结构、联合与枚举——只引书名;
- C11 标准 6.5.2.3(union 成员访问与实现定义注 95)——标准原文为准;
- 交叉引用预告:6.1.2 IP/端口/字节序——htonl/ntohl 网络字节序转换;
- 下一章预告:1.3.8 枚举与 typedef 工程写法——用枚举替换魔法数字。
迁移训练(migration training)
把本章技能迁移到 ARM 板卡 与 网络程序:
| 环节 | 本机 x86_64 | ARM(默认小端) | 网络传输 |
|---|---|---|---|
| 大小端判断 | 小端(实测 0x44) | 通常小端(部分处理器可配大端) | 统一网络字节序(大端) |
| int↔字节数组 | memcpy 得到本机序 | 同左 | htonl/ntohl 转换后收发 |
| union 类型双关 | 本平台自检可用 | 已知平台可用 | 不用于跨平台传输 |
不变的:union 共享内存机制、memcpy 互转写法、大小端的概念定义;要变的:字节序的实际方向——跨平台代码永远显式转换,不依赖默认方向。
内容来源映射
| 内容部分 | 资料 | 位置 | 标记 | 说明 |
|---|---|---|---|---|
| union 定义与共享内存、与 struct 的区别、大小端概念与判断 | 第一阶段讲义 | 22.x + 13.5 节 | 【来源】 | 正文在原资料基础上重新组织表述,未大段复制原文 |
| union 类型双关(type punning)的可移植性边界、memcpy 安全替代、网络字节序衔接 | 无 | 【补充】 | 资料未讲 union 读写不同成员的实现定义性质;本章补可移植性说明与 memcpy 逐字节互转 | |
| 小练习 / 章节测验 / 实践任务 / 面试问题 / 延伸阅读 | 无 | 【补充】 | 原资料该章无成体系练习,全部新编 |