对象和对齐
C 程序创建、销毁、访问和操作对象。
C 中的对象是执行环境中 数据存储 的一个区域,其内容可以表示值(当解释为具有特定 类型 时,值是对象的“内容”的含义)。
每个对象都有
- 大小(可以使用
sizeof
确定) - 对齐要求(可以使用
_Alignof
确定)(自 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 | 应用于 | 已发布的行为 | 正确行为 |
---|---|---|---|
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++ 文档 对象
|