Object Model


对象模型是: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