对象表示
本节介绍对象表示 (Object representation) 与值表示 (Value representation)。
你可以把它们理解为:
- 对象表示:对象在内存(抽象机器的字节序列)中的“原始字节形态”。
- 值表示:在这些字节形态中,哪些位模式 (Bit pattern) 对应“有效的值”。
这一区分解释了很多看似奇怪但非常重要的现象:例如“为什么同样是 4 字节,某些位模式对 float 是陷阱表示 (Trap representation)?”、“为什么结构体中会出现填充字节 (Padding)?”。
1. 对象表示 vs 值表示
1.1 对象表示 (Object representation)
对象表示是对象占据的那段连续字节的内容。标准允许你通过字符类型(char、signed char、unsigned char)观察并复制对象表示。
1.2 值表示 (Value representation) 与填充位
对某些类型而言,并非所有位模式都对应一个合法值:
- 某些位可能是填充位 (Padding bit);
- 某些位模式可能是陷阱表示 (Trap representation)。
当一个对象的对象表示对应陷阱表示时,对该对象按其类型进行求值(读出“值”)可能触发 UB。
实践建议
除非你确实在做底层库(序列化、校验、哈希、协议),否则应尽量避免依赖“具体对象表示”。对象表示通常是实现定义的(例如大小端、浮点格式)。
2. 观察对象表示:标准写法
2.1 使用 unsigned char 查看字节
c
#include <stddef.h>
#include <stdio.h>
static void dump_bytes(const void* p, size_t n) {
const unsigned char* b = (const unsigned char*)p;
for (size_t i = 0; i < n; i++) {
printf("%02x%s", (unsigned)b[i], (i + 1 == n) ? "" : " ");
}
putchar('\n');
}
int main(void) {
unsigned x = 0x11223344u;
dump_bytes(&x, sizeof x);
return 0;
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
该输出与机器的字节序 (Endianness) 有关:
- 若是小端序,通常输出
44 33 22 11; - 若是大端序,通常输出
11 22 33 44。
注意:字节序属于实现定义行为 (Implementation-defined behavior)。
2.2 使用 memcpy 在类型之间搬运对象表示
如果你想“把某个对象的对象表示解释成另一种类型”,标准最稳妥的方式是 memcpy(而不是指针强转后解引用)。
示例见 9.1 内存模型 中的 float / uint32_t 示例。
3. 对齐相关
对象表示与对齐密切相关:对齐决定了对象能出现在哪些地址上,也影响结构体内部的填充布局。
4. 习题
- 写程序分别观察
uint16_t、uint32_t、uint64_t的字节序输出,并总结你的平台是大端还是小端。 - 写程序观察以下类型的
sizeof与alignof(见 9.2.2):char、int、double、max_align_t。 - 设计一个“结构体布局观察”实验:对
struct { char a; int b; char c; }输出:sizeof;offsetof(需要<stddef.h>);- 对象表示(建议先把对象整体置零,再写入成员值)。