C的加载过程与内存模型

处理过程

预处理→编译→汇编→链接→装载→执行

预处理 : 处理以 # 开头的预处理指令,例如 #include#define#ifdef 等。

  • 宏展开
  • 文件包含展开
  • 条件编译判断

编译 : 将预处理后的 C 代码转换成汇编代码。

  • 语法分析(解析语法结构)
  • 语义分析(变量类型检查)

汇编 : 将汇编代码转换为目标文件(二进制格式),还不是最终的可执行程序。

链接 : 将一个或多个 .o 文件与所需的库文件(比如标准库 libc)合并,解决函数调用、变量引用等符号地址。

装载 : 将链接生成的可执行文件加载到内存中,准备好运行环境。

执行 : 程序入口通常是 _start 函数,由链接器提供,之后会调用 main()

  • 用户编写的程序从 main() 开始运行。
  • 程序执行时:
    • 初始化全局变量
    • 执行 main() 函数
    • 程序返回时调用 exit()

加载后的C语言内存结构

C语言通过链接和装载 将数据分布到一些固定的位置

.text 代码段 用于放置只读的字节码指令 .rodata 段也会放置在附近

.data 数据段 用于存放已经初始化全局变量和静态变量

.bss 段 用于存放未初始化和初始化为0的全局变量和静态变量。

堆区 用于存放程序执行后动态分配的内存

image

变量,指针与类型

在 C 语言中,变量的声明意味着:编译器会在内存中为这个变量分配一块空间,并为它分配一个可供程序访问的“名称”或“标签”。这个标签(变量名)本质上是对该内存地址的一种静态引用方式

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
int c = 0 ; 
// 为变量 c 分配了一个 int 类型(通常是 4 字节)的空间,并初始化为 0

char a[] = {'h','e','l','l','o','\0'};
// 分配了一个大小为 sizeof(a) 的字符数组,内存上是一块连续空间,存储了具体的字符数据
typedef struct _person{
int age ;
//char name[10]; 无法赋值
char* name ;
} person;
// 为结构体变量 h 分配了一块空间,包含了 int(age)和指针(name)两个成员
person h;
h.name = "codfish";
// 找到结构体中的char指针类型结构name, 将该指针指向“codfish” 的地址
h.age = 12;
// 找到结构体中的intl类型age, 向其所在的内存地址写入“12”

变量名可以看作是对该内存空间的一个静态引用标签,通过它我们可以对这块内存进行读写。

在这个视角下,“地址”本身也可以看作是一种值,而指针类型就是专门用于存储地址值的类型。指针变量的值是一个地址,通过这个地址可以间接访问另一个变量或内存区域。因此,指针本质上是一种间接寻址的变量类型

1
2
3
int a = 3 ;   // 开辟了一块内存空间,存储的值为3
int * p = &a ; // 开辟了一块内存空间, 存储的值为a的存储地址
printf("%d",*p); // 打印 p 指向地址中存储的值,即 a

在 C 语言中,左值(lvalue)代表的是一个可寻址、可写入的内存位置,是程序运行中频繁操作的对象。而右值(rvalue)通常是一个临时值,不具备可寻址性,用完即弃。

例如:

  • 变量 a 是左值,你可以取地址 &a,也可以对它赋值。
  • 表达式 a + 1 是右值,它是一个计算结果,不能取地址。
  • 字符串 "codfish" 是一个右值常量,其内容在编译期就固定,通常被保存在 只读数据段(.rodata) 中。

因此:

左值是程序运行时可操作的内存实体,而右值更多地体现为临时性和只读性。其中某些右值(如字符串字面量、常量表达式)确实会保存在 .rodata 段中,但大多数右值只是临时计算结果,可能存在于寄存器中,或者根本不会在物理内存中长期保留。

最后回到上文,在结构体中如果定义的char name[10];则无法使用字符串直接完成初始化。因为这时 结构体中定义的是一个 10个字节长度 char 数组,而接收的右值是一个字符指针。两边的类型并不能匹配,所以无法完成赋值,只能对该数组中的每一位分别进行赋值。

1
2
3
4
5
6
7
8
9
10
11
12
13
human.name[0] = 'C';
human.name[1] = 'o';
human.name[2] = 'd';
human.name[3] = 'f';
human.name[4] = 'i';
human.name[5] = 's';
human.name[6] = 'h';
human.name[7] = '\0';

// 输出
[root@iZbp19tqlmjz1dmnm8w43uZ myc]# ./test
hello ,12 years old's Codfish


C的加载过程与内存模型
http://gadoid.io/2025/04/11/C语言执行过程与内存模型/
作者
Codfish
发布于
2025年4月11日
许可协议