C的加载过程与内存模型
处理过程
预处理→编译→汇编→链接→装载→执行
预处理 : 处理以 #
开头的预处理指令,例如 #include
、#define
、#ifdef
等。
- 宏展开
- 文件包含展开
- 条件编译判断
编译 : 将预处理后的 C 代码转换成汇编代码。
- 语法分析(解析语法结构)
- 语义分析(变量类型检查)
汇编 : 将汇编代码转换为目标文件(二进制格式),还不是最终的可执行程序。
链接 : 将一个或多个 .o
文件与所需的库文件(比如标准库 libc
)合并,解决函数调用、变量引用等符号地址。
装载 : 将链接生成的可执行文件加载到内存中,准备好运行环境。
执行 : 程序入口通常是 _start
函数,由链接器提供,之后会调用 main()
。
- 用户编写的程序从
main()
开始运行。 - 程序执行时:
- 初始化全局变量
- 执行
main()
函数 - 程序返回时调用
exit()
加载后的C语言内存结构
C语言通过链接和装载 将数据分布到一些固定的位置
.text 代码段 用于放置只读的字节码指令 .rodata 段也会放置在附近
.data 数据段 用于存放已经初始化全局变量和静态变量
.bss 段 用于存放未初始化和初始化为0的全局变量和静态变量。
堆区 用于存放程序执行后动态分配的内存
变量,指针与类型
在 C 语言中,变量的声明意味着:编译器会在内存中为这个变量分配一块空间,并为它分配一个可供程序访问的“名称”或“标签”。这个标签(变量名)本质上是对该内存地址的一种静态引用方式。
例如:
1 |
|
变量名可以看作是对该内存空间的一个静态引用标签,通过它我们可以对这块内存进行读写。
在这个视角下,“地址”本身也可以看作是一种值,而指针类型就是专门用于存储地址值的类型。指针变量的值是一个地址,通过这个地址可以间接访问另一个变量或内存区域。因此,指针本质上是一种间接寻址的变量类型。
1 |
|
在 C 语言中,左值(lvalue)代表的是一个可寻址、可写入的内存位置,是程序运行中频繁操作的对象。而右值(rvalue)通常是一个临时值,不具备可寻址性,用完即弃。
例如:
- 变量
a
是左值,你可以取地址&a
,也可以对它赋值。 - 表达式
a + 1
是右值,它是一个计算结果,不能取地址。 - 字符串
"codfish"
是一个右值常量,其内容在编译期就固定,通常被保存在 只读数据段(.rodata) 中。
因此:
左值是程序运行时可操作的内存实体,而右值更多地体现为临时性和只读性。其中某些右值(如字符串字面量、常量表达式)确实会保存在 .rodata 段中,但大多数右值只是临时计算结果,可能存在于寄存器中,或者根本不会在物理内存中长期保留。
最后回到上文,在结构体中如果定义的char name[10];则无法使用字符串直接完成初始化。因为这时 结构体中定义的是一个 10个字节长度 char 数组,而接收的右值是一个字符指针。两边的类型并不能匹配,所以无法完成赋值,只能对该数组中的每一位分别进行赋值。
1 |
|
C的加载过程与内存模型
http://gadoid.io/2025/04/11/C语言执行过程与内存模型/