对象模型是:C 语言如何在“抽象定义”与“真实机器内存”之间建立对应关系
换句话说:C 如何描述“一段内存,在什么时候,能被谁、以什么方式访问
这套描述,就是C 的对象模型
Object
在C 的语义中:对象 = 在程序执行期间存在的一段存储区域,其内容可以表示一个值
关键点:
- 执行期间 -> 生命周期
- 存储区域 -> 有地址、有大小
- 表示一个值 -> 必须能被类型解释
因此,没有存储期的东西,不是对象;不能被类型解释的东西,不是对象
对象是存储,不是值
对象不是数值、不是类型、不是名字
int x = 10;
x:标识符(名字)int:类型(解释规则)- 对象:一段内存(通常4字节)
对象只是那4个字节本身,10只是这4个字节在int规则下被解释出来的结果
换句话说,对象 = 内存;值 = 内存 + 类型解释
对象必须存在一段时间
对象一定有存储期(storage duration)
void f() {
int x;
}
- 进入函数 ->
x的对象出现 - 离开函数 -> 对象消失
这就是”对象存在“的时间窗口
对比
# define N 10
N没有对象,它从来没有”存在于内存中“
对象 =/ 变量
变量 = 具名对象
对象 =/ 一定有名字
int x;
- 有对象,有名字 -> 变量
malloc(sizeof(int));
- 有对象,没有名字;只能通过指针访问它
struct S {
int a;
int b;
};
struct S本身不是对象a,b是子对象(subobject)
对象是层级结构的
字符串字面量也是对象
"hello"
这是一个
char[6]对象- 静态存储期
- 没有名字
- 不能修改(修改是UB)
这解释了为什么
char *p = "hello";
拿到的是指向一个对象的指针,不是“字符串本身”
对象的三大支柱
C 的对象模型完全由这三件事支撑
1. 存储期(Storage Duration)
对象在什么时候存在,这是第一性问题
四种存储期
1. 自动存储期
void f() {
int x;
}
- 进入作用域 -> 对象出现
- 离开作用域 -> 对象消失
- 通常在栈上
2. 静态存储期
static int x;
int g;
- 程序启动前分配
- 程序结束时销毁
.data/ .bss/ .rodata
3. 动态存储期
int *p = malloc(sizeof *p);
- 运行期创建
free之前一直存在- 生命周期不依赖作用域
4. 线程存储期(C11)
_Thread_local int x;
- 每个线程一份对象
- 生命周期 = 线程
存储期决定:对象“在不在”
类型(Type)
对象本身只是比特,类型是解释规则,决定
- 对象占多大空间
- 对齐要求
- 读写方式
- 运算规则
- 是否允许别名
int x
- 对象:4字节(假设)
- 类型:
int - “10"只是解释结果,不是对象的一部分
类型不是对象的属性,而是访问对象时使用的规则。同一对象,用不同类型访问 -> UB或不同语义
标识与访问路径(Name/Lvalue)
对象可能有名字,没名字,有多个访问路径
int x;
int *p = &x;
- 一个对象
- 两条访问路径
x和*p必须遵守类型系统一致性
这就是strict aliasing的根源
子对象模型
C的对象不是扁平的,是分层结构
数组对象
int a[4];
- 一个数组对象
- 包含4个
int子对象 - 内存连续
数组不是“指针集合”,是一个整体对象
结构体对象
struct S {
int a;
char b;
};
- 一个 struct 对象
- 两个成员子对象
- 中间可能有padding(也是对象的一部分)
padding属于对象,但不属于任何子对象
联合体对象(union)
union U {
int i;
float f;
};
- 一个对象
- 多个成员重叠
- 一次只能有一个“有效成员”
对象可以嵌套,但规则仍是:一段内存 + 存在时间
对象 vs 表达式
x + 1
这不是对象
- 没有地址
- 没有存储期
- 只是一个临时计算结果
即使编译器真的用寄存器存了它,在语言语义上,它仍然不是对象
对象是语言层面的概念,不是”有没有寄存器“
对象与生命周期规则
几乎所有 C 的未定义行为,都来自对象模型被破坏
生命周期结束后访问
int *p;
{
int x = 10;
p = &x;
}
*p = 20; // UB
对象已不存在
越界访问
int a[4];
a[4] = 1; // UB
对象模型之外
非法类型访问
int x;
float *p = (float*)&x;
*p = 1.0f; // UB (严格别名)
违反对象的“有效类型”
malloc 的特殊地位
void *p = malloc(16);
此时:
- 对象存在
- 没有有效类型
第一次通过某种类型访问它
int *q = p;
*q = 10;
此时,对象的有效类型变为int
malloc 返回的是“未定型对象”
C 的对象模型实际上是程序员、编译器、硬件之间的三方契约
C 故意不等同于真实硬件
- 程序员视角:逻辑对象,有生命周期和类型语义
- 编译器视角:优化许可,可重排、可消除、可别名分析
- 硬件视角:最终映射,寄存器、内存、缓存的实际使用
这种分离正是C即能在高层抽象又能在底层操作的核心机制
C 的所有规则都围绕对象展开
没有对象模型,就不可能
- 定义 UB
- 允许 aggressive optimization
- 做别名分析
- 做栈/堆抽象
- 写可移植的底层代码
在C 语言里,没有对象,就没有未定义行为;没有未定义行为,就没有 C