Objects and alignment
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* 和 double* 不能互为别名 void f1(int* pi, double* pd, double d) { // 对 *pi 的读取只能在循环开始前执行一次 for (int i = 0; i < *pi; i++) *pd++ = d; }
struct S { int a, b; }; // int* 和 struct S* 可能存在别名,因为 S 是包含 int 类型成员的聚合类型 void f2(int* pi, struct S* ps, struct S s) { // 对 *pi 的读取必须在所有通过 *ps 的写入之后进行 for (int i = 0; i < *pi; i++) *ps++ = s; }
注意,即使上述规则允许两个指针互为别名,也可以使用 restrict 限定符 来表明它们不会互为别名。
请注意,类型双关也可以通过 联合体 的非活跃成员来执行。
对齐
每个完整的 对象类型 都具有称为 对齐要求 的属性,这是一个类型为 size_t 的整数值,表示可以分配该类型对象的连续地址之间的字节数。有效的对齐值是非负的二次幂整数。
| (C11起) |
为了满足结构体所有成员的对齐要求,可能会在某些成员之后插入填充字节。
#include <stdalign.h> #include <stdio.h> // struct S 的对象可以在任何地址分配 // 因为 S.a 和 S.b 都可以在任何地址分配 struct S { char a; // 大小: 1, 对齐: 1 char b; // 大小: 1, 对齐: 1 }; // 大小: 2, 对齐: 1 // struct X 的对象必须在 4 字节边界上分配 // 因为 X.n 必须在 4 字节边界上分配 // 因为 int 的对齐要求通常为 4 struct X { int n; // 大小: 4, 对齐: 4 char c; // 大小: 1, 对齐: 1 // 三个字节填充 }; // 大小: 8, 对齐: 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 起) 。
基础对齐适用于所有存储持续期类型的对象。
|
若通过
若结构体或联合体类型
所有
|
(since 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 对齐(页码:3)
-
- 3.14 对象(页码:5)
-
- 6.2.6 类型表示(页码:37-39)
-
- 6.5/6-7 表达式(页码:67-68)
- C89/C90 标准 (ISO/IEC 9899:1990):
-
- 1.6 术语定义
参见
|
C++ 文档
关于
Object
|