跳到主要内容
🔍
1.3.7已发布intermediate · 约 2.5 课时 · P0

共用体与大小端实战

掌握 union 共享内存机制与大小端判断,学会 int 与字节数组的安全互转(网络字节序前置)

  • 来源
  • 补充

标记说明:【来源】来自上传资料 · 【补充】课程新编 · 【纠错】按勘误表修正 · 【更新】过时内容已现代化 · 【待确认】无法可靠还原

完成标准(本章)

  • 📖 已阅读:滚动 ≥ 80% 且有效阅读 ≥ 180
  • ✏️ 已练习:小练习正确率 ≥ 60%
  • 📝 已通过测验:分数 ≥ 60
  • 🛠️ 已掌握还需完成实践任务
学习状态:未开始

1.3.7 共用体与大小端实战

本章来源:union 定义与共享内存、与 struct 的区别、大小端概念与判断来自《第一阶段讲义》22.x + 13.5 节【来源】,经重新组织表述;union 类型双关(type punning)的可移植性边界与 memcpy 安全替代为资料未覆盖的新编【补充】。平台适用性 universal——但大小端判断结果随平台不同,本章输出为本机(x86_64 小端)实测。

① 学习目标

  1. 说出 union 与 struct 的本质区别(成员共享同一块内存 vs 成员各占各的);
  2. 解释大小端概念,并写出"指针法/共用体法"判断本机字节序的程序;
  3. 说明"判断字节序"程序的实现定义性质——工程上常见,但不把某次输出当语言保证;
  4. 用 memcpy 实现 int 与 4 字节数组的安全互转(避免 union 类型双关的可移植性争议);
  5. 理解字节序与网络字节序的衔接(6.1.2 前置)。

② 前置知识

  • 必选:1.3.6 结构体与位段(union 语法与 struct 同源;->/. 访问一致)。

③ 核心概念【来源】

概念说明
union 定义与共享内存union Word { unsigned int u; unsigned char b[4]; };——所有成员从同一地址开始,union 大小 = 最大成员的大小;任一时刻只应有一个成员是"当前有效"的
union vs structstruct 成员同时存在(各占内存);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 当整数读位模式。这个写法的问题主要是?(单选)

    ◌ 未作答

⑨ 章节测验

章节测验

5 题题库 · 随机抽 5 题 · 及格线 60 分 · 前端判分(学习自测)
开始测验 →

⑩ 实战任务

实践任务

大小端判断与 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_64ARM(默认小端)网络传输
大小端判断小端(实测 0x44)通常小端(部分处理器可配大端)统一网络字节序(大端)
int↔字节数组memcpy 得到本机序同左htonl/ntohl 转换后收发
union 类型双关本平台自检可用已知平台可用不用于跨平台传输

不变的:union 共享内存机制、memcpy 互转写法、大小端的概念定义;要变的:字节序的实际方向——跨平台代码永远显式转换,不依赖默认方向。

内容来源映射

内容部分资料位置标记说明
union 定义与共享内存、与 struct 的区别、大小端概念与判断第一阶段讲义22.x + 13.5 节来源正文在原资料基础上重新组织表述,未大段复制原文
union 类型双关(type punning)的可移植性边界、memcpy 安全替代、网络字节序衔接补充资料未讲 union 读写不同成员的实现定义性质;本章补可移植性说明与 memcpy 逐字节互转
小练习 / 章节测验 / 实践任务 / 面试问题 / 延伸阅读补充原资料该章无成体系练习,全部新编