对象与对齐
C 程序创建、销毁、访问和操作对象。
C 中的对象是执行环境中数据存储的一个区域,其内容可以表示值(值是对象内容在被解释为具有特定类型时的含义)。
每个对象都具有
- 大小(可以用
sizeof
确定) - 对齐要求(可以用
_Alignof
(直到 C23)alignof
(自 C23 起) 确定)(自 C11 起) - 存储期(自动、静态、分配、线程局部)
- 生命周期(等于存储期或临时)
- 有效类型(见下文)
- 值(可能不确定)
- 可选地,一个表示此对象的标识符。
对象通过声明、分配函数、字符串字面量、复合字面量以及返回带有数组成员的结构体或联合体的非左值表达式来创建。
目录 |
[编辑] 对象表示
除了位域之外,对象由连续的一个或多个字节序列组成,每个字节包含 CHAR_BIT 位,并且可以用 memcpy 复制到 unsigned char[n] 类型的对象中,其中 n
是对象的大小。结果数组的内容被称为对象表示。
如果两个对象具有相同的对象表示,则它们比较相等(除了浮点 NaN)。反之不成立:两个比较相等的对象可能具有不同的对象表示,因为并非对象表示的每个位都需要参与值。这些位可能用于填充以满足对齐要求、进行奇偶校验、指示陷阱表示等。
如果对象表示不表示对象类型的任何值,则称其为陷阱表示。以除通过字符类型的左值表达式读取之外的任何方式访问陷阱表示都是未定义行为。结构体或联合体的值绝不是陷阱表示,即使其任何特定成员是陷阱表示。
对于 char、signed char 和 unsigned char 类型的对象,对象表示的每个位都必须参与值表示,并且每个可能的位模式表示一个不同的值(不允许填充、陷阱位或多种表示)。
当整型对象(short、int、long、long long)占用多个字节时,这些字节的使用是实现定义的,但两种主要实现是大端(POWER、Sparc、Itanium)和小端(x86、x86_64):大端平台将最高有效字节存储在整数所占用存储区域的最低地址,小端平台将最低有效字节存储在最低地址。详细信息请参阅字节序。另请参阅下面的示例。
尽管大多数实现不允许整型类型的陷阱表示、填充位或多种表示,但也有例外;例如,Itanium 上的整型值可能是陷阱表示。
[编辑] 有效类型
每个对象都有一个有效类型,它决定了哪些左值访问是有效的,哪些违反了严格别名规则。
如果对象是通过声明创建的,则该对象的声明类型是该对象的有效类型。
如果对象是通过分配函数(包括 realloc)创建的,则它没有声明类型。此类对象按如下方式获取有效类型
- 首次通过非字符类型的左值对该对象进行写入,此时该左值的类型成为此对象用于该写入和所有后续读取的有效类型。
- memcpy 或 memmove 将另一个对象复制到该对象中,或将另一个对象作为字符类型数组复制到该对象中,此时源对象的有效类型(如果它有)成为此对象用于该写入和所有后续读取的有效类型。
- 任何其他对没有声明类型的对象的访问,有效类型是用于访问的左值的类型。
[编辑] 严格别名
给定一个具有有效类型 T1 的对象,使用不同类型 T2 的左值表达式(通常是解引用指针)是未定义行为,除非
- T2 和 T1 是兼容类型。
- T2 是与 T1 兼容的类型的 cvr-限定版本。
- T2 是与 T1 兼容的类型的有符号或无符号版本。
- T2 是聚合类型或联合体类型,其成员中包含上述类型之一(包括递归地,子聚合或包含联合体的成员)。
- T2 是字符类型(char、signed char 或 unsigned char)。
这些规则控制着在编译一个接收两个指针的函数时,编译器是否必须在通过一个指针写入后重新读取另一个指针。
// int* and double* cannot alias void f1(int* pi, double* pd, double d) { // the read from *pi can be done only once, before the loop for (int i = 0; i < *pi; i++) *pd++ = d; }
struct S { int a, b; }; // int* and struct S* may alias because S is an aggregate type with a member of type int void f2(int* pi, struct S* ps, struct S s) { // read from *pi must take place after every write through *ps for (int i = 0; i < *pi; i++) *ps++ = s; }
请注意,即使上述规则允许它们别名,restrict 限定符也可以用于指示两个指针不别名。
请注意,类型双关也可以通过联合体的非活跃成员执行。
[编辑] 对齐
每个完整的对象类型都有一个名为对齐要求的属性,它是一个 size_t 类型的整数值,表示该类型对象可以分配的连续地址之间的字节数。有效的对齐值是非负的2的整数幂。
(C11 起) |
为了满足结构体所有成员的对齐要求,可能在其某些成员之后插入填充。
#include <stdalign.h> #include <stdio.h> // objects of struct S can be allocated at any address // because both S.a and S.b can be allocated at any address struct S { char a; // size: 1, alignment: 1 char b; // size: 1, alignment: 1 }; // size: 2, alignment: 1 // objects of struct X must be allocated at 4-byte boundaries // because X.n must be allocated at 4-byte boundaries // because int's alignment requirement is (usually) 4 struct X { int n; // size: 4, alignment: 4 char c; // size: 1, alignment: 1 // three bytes padding }; // size: 8, alignment: 4 int main(void) { printf("sizeof(struct S) = %zu\n", sizeof(struct S)); printf("alignof(struct S) = %zu\n", alignof(struct S)); printf("sizeof(struct X) = %zu\n", sizeof(struct X)); printf("alignof(struct X) = %zu\n", alignof(struct X)); }
可能的输出
sizeof(struct S) = 2 alignof(struct S) = 1 sizeof(struct X) = 8 alignof(struct X) = 4
每个对象类型都对其该类型的每个对象施加其对齐要求。最弱(最小)的对齐是 char、signed char 和 unsigned char 类型的对齐,等于 1。任何类型的最严格(最大)基本对齐是实现定义的并等于 max_align_t 的对齐(自 C11 起)。
所有存储期类型的对象都支持基本对齐。
如果使用 如果结构体或联合体类型 |
(C11 起) |
[编辑] 缺陷报告
以下行为改变的缺陷报告被追溯地应用于以前发布的 C 标准。
缺陷报告 | 应用于 | 发布时的行为 | 正确的行为 |
---|---|---|---|
DR 445 | C11 | 类型可能具有扩展对齐,而无需涉及 _Alignas | 它必须具有基本对齐 |
[编辑] 参考
- C17 标准 (ISO/IEC 9899:2018)
- 3.15 对象 (p: 5)
- 6.2.6 类型表示 (p: 33-35)
- 6.2.8 对象的对齐 (p: 36-37)
- 6.5/6-7 表达式 (p: 55-56)
- C11 标准 (ISO/IEC 9899:2011)
- 3.15 对象 (p: 6)
- 6.2.6 类型表示 (p: 44-46)
- 6.2.8 对象的对齐 (p: 48-49)
- 6.5/6-7 表达式 (p: 77)
- C99 标准 (ISO/IEC 9899:1999)
- 3.2 对齐 (p: 3)
- 3.14 对象 (p: 5)
- 6.2.6 类型表示 (p: 37-39)
- 6.5/6-7 表达式 (p: 67-68)
- C89/C90 标准 (ISO/IEC 9899:1990)
- 1.6 术语定义
[编辑] 另请参阅
C++ 文档 关于 对象
|