第一章:温故而知新
第一章:温故而知新
1.1 从 Hello World 说起
程序从源代码到可执行文件的完整过程
让我们通过一个简单的 Hello World 程序来了解整个编译过程:
1 | #include <stdio.h> |
1. 预处理(Preprocessing)
预处理阶段主要处理以下内容:
- 处理所有的预处理指令(以 # 开头的指令)
- 展开所有的宏定义
- 处理条件编译指令
- 删除注释
可以使用以下命令查看预处理后的文件:
1 | gcc -E hello_world.c -o hello_world.i |
2. 编译(Compilation)
编译阶段将预处理后的文件转换为汇编代码:
1 | gcc -S hello_world.i -o hello_world.s |
生成的汇编代码大致如下:
1 | .section __TEXT,__text,regular,pure_instructions |
3. 汇编(Assembly)
汇编阶段将汇编代码转换为机器码:
1 | gcc -c hello_world.s -o hello_world.o |
4. 链接(Linking)
链接阶段将目标文件与所需的库文件链接,生成可执行文件:
1 | gcc hello_world.o -o hello_world |
编译、链接、装载的基本概念
编译
编译是将高级语言转换为汇编语言的过程,主要包括:
- 词法分析:将源代码分解成标记(token)
- 语法分析:将标记组织成语法树
- 语义分析:检查语法树的语义正确性
- 中间代码生成:生成中间表示
- 目标代码生成:生成汇编代码
链接
链接是将多个目标文件合并成一个可执行文件的过程,主要包括:
- 地址和空间分配
- 符号决议
- 重定位
装载
装载是将可执行文件加载到内存中运行的过程,主要包括:
- 创建进程
- 分配虚拟地址空间
- 加载可执行文件
- 设置程序入口点
1.2 万变不离其宗
计算机系统的基本组成
硬件系统
CPU(中央处理器)
- 控制单元
- 运算单元
- 寄存器组
内存
- RAM(随机访问存储器)
- ROM(只读存储器)
- 缓存
外设
- 输入设备
- 输出设备
- 存储设备
软件系统
操作系统
- 进程管理
- 内存管理
- 文件系统
- 设备驱动
应用程序
- 系统程序
- 用户程序
系统库
- 标准库
- 运行时库
- 第三方库
程序运行的基本原理
程序加载
可执行文件格式
- ELF(Linux)
- PE(Windows)
- Mach-O(macOS)
程序入口点
- 主函数入口
- 启动代码
- 初始化代码
内存映射
- 代码段
- 数据段
- 堆
- 栈
程序执行
指令执行
- 取指令
- 译码
- 执行
- 写回
数据访问
- 寄存器访问
- 内存访问
- 外设访问
系统调用
- 进程控制
- 文件操作
- 设备操作
- 网络通信
程序终止
正常退出
- 返回主函数
- 调用 exit()
- 调用 _exit()
异常处理
- 信号处理
- 异常处理
- 错误处理
资源释放
- 内存释放
- 文件关闭
- 设备释放
实践练习
- 编译过程实验
1 | # 1. 预处理 |
- 查看文件信息
1 | # 查看可执行文件信息 |
思考题
- 为什么需要预处理阶段?预处理阶段主要处理哪些内容?
- 编译和汇编的区别是什么?
- 链接阶段的主要任务是什么?
- 程序加载到内存后,内存布局是怎样的?
- 系统调用和普通函数调用的区别是什么?
参考资料
- 《程序员的自我修养:链接、装载与库》
- 《深入理解计算机系统》
- 《编译原理》(龙书)
- GCC 在线文档
- ELF 文件格式规范