1. 翻译环境和运行环境
- 翻译环境:预编译+编译+汇编+链接
1. 翻译环境和运行环境
在标准 C(ANSI C)中,程序处理分为两个独立环境:
1.翻译环境 :将.c源代码,转换为二进制的可执行机器指令(编译 + 链接阶段)。
2.运行环境 :执行可执行程序,输出运行结果。
**整体流程:**多个.c源文件 → 翻译环境 → 可执行程序 → 运行环境 → 输出结果
2.翻译环境
翻译环境是由编译和链接 两个大的过程组成的,而编译 又可以分解成:预处理(有些书也叫预编
译)、编译、汇编三个过程。
1编译阶段
多个.c文件单独编译,每个文件生成目标文件
Windows 系统 :后缀为.obj
Linux 系统 :后缀为.o
**工具:**编译器(Windows 常用cl.exe,Linux 常用gcc)
2链接阶段
参与文件: 所有编译生成的目标文件 + 链接库(系统运行时库、第三方库)
工具: 链接器(Windows 常用link.exe)
最终产物:可执行程序(Windows 为.exe,Linux 为无后缀可执行文件)
2.1预处理(预编译****)
核心作用是处理源文件 / 头文件的预编译指令,生成后缀为 .i 的纯 C 代码文件。
GCC 实操命令
cpp
gcc -E test.c -o test.i
主要处理规则
1.宏处理 :删除#define,完全展开所有宏定义;
2.条件编译 :处理#if #ifdef #elif #else #endif;
3.头文件嵌入 :递归展开#include,把头文件内容直接插入源码;
4.清理注释 :删除所有单行 / 多行注释;
5.调试标记:添加行号、文件名标识,用于后续调试;
6.保留指令 :保留#pragma编译器指令。
!!! 排查技巧:宏定义、头文件包含异常时,可直接查看.i文件定位问题。
2.2编译
核心作用是对预处理后的.i文件,依次做词法分析→语法分析→语义分析→代码优化,生成汇编代码文件(.s)。
GCC 实操命令
cpp
gcc -S test.i -o test.s
2.2.1 词法分析
原理: 扫描器将代码字符切割为记号(关键字、标识符、字面量、运算符等);
**示例:**代码 arrayindex = (index+4)*(2+6);经过词法分析后,会被拆分为16 个独立记号,完成字符到代码单元的转换。
后续编译流程词法分析后还会执行:
语法分析 :构建语法树,检查语法格式是否合法;
语义分析 :检查类型匹配、作用域、表达式合法性;
代码优化:简化冗余计算(示例中2+6会直接优化为8)。
2.3汇编
核心作用是将编译生成的汇编代码(.s),翻译为二进制机器指令,生成目标文件。
特点 :逐条翻译、几乎不做优化,一条汇编语句对应一条机器指令。
文件后缀:Linux 为 .o,Windows 为 .obj
GCC 实操命令
cpp
gcc -c test.s -o test.o
2.4链接
核心作用是将多个目标文件 + 系统库文件整合,解决多文件 / 多模块调用问题,最终生成可执行程序。
三大核心步骤1.地址和空间分配: 统一规划所有代码、数据的内存布局
2.符号决议: 匹配函数、全局变量的声明与定义
**3.重定位:**修正跨模块调用的地址,补全真实内存地址
2.4.1实例解析(test.c + add.c)
cpp
#include <stdio.h>
//test.c
//声明外部函数
extern int Add(int x, int y);
//声明外部的全局变量
extern int g_val;
int main()
{
int a = 10;
int b = 20;
int sum = Add(a, b);
printf("%d\n", sum);
return 0;
}
add.c
int g_val = 2022;
int Add(int x, int y)
{
return x+y;
}
test.c 用 extern 声明外部函数 Add、全局变量 g_val,但编译时不知道真实地址;
add.c 定义 Add 函数和 g_val 全局变量;
两个文件分别编译生成 test.o、add.o;
链接器找到 Add、g_val 真实地址,修正 test.o 中的调用指令,该过程即重定位。
2.5运行环境
一.程序载入内存
操作系统环境(Windows/Linux):由操作系统自动完成,操作系统从磁盘读取可执行文件,分配内存空间,将程序加载到内存。裸机 / 独立环境(单片机、嵌入式):无操作系统,需手动载入,或直接将编译后的可执行代码烧录到只读存储器(ROM/Flash)中。
二.启动执行,调用main函数
程序加载完成后,系统 / 硬件初始化完成,自动调用main()函数,main是 C 程序的唯一入口。
三.代码执行,内存使用
程序运行时主要用到两类内存:
1.运行时栈(Stack)
用于存储局部变量、函数调用返回地址
函数调用时自动分配,函数结束后自动释放,生命周期随函数。
2.静态内存(static)
存储static修饰的静态变量、全局变量
程序整个运行期间都保留值,不会随函数结束释放
2.6程序终止
1.正常终止: main函数执行完毕返回(return 0)。
**2.意外终止:**程序崩溃、强制关闭、异常报错等。

