对象与对齐
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):大端序平台将最高有效字节存储在整数占用的存储区域的最低地址,小端序平台将最低有效字节存储在最低地址。有关详细信息,请参阅 Endianness。另请参见下面的示例。
虽然大多数实现不允许整数类型的陷阱表示、填充位或多重表示,但也存在例外;例如,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 | 应用于 | 已发布行为 | 正确行为 |
---|---|---|---|
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++ 文档 关于 对象
|