C语言自学--编译和链接

目录

1、翻译环境和运行环境

[2、 翻译环境:预编译+编译+汇编+链接](#2、 翻译环境:预编译+编译+汇编+链接)


1、翻译环境和运行环境

在ANSIC的任何一种实现中,存在两个不同的环境:

第1种是翻译环境,在这个环境中源代码被转换为可执行的机器指令。

第2种是执行环境,它用于实际执行代码。


2、 翻译环境:预编译+编译+汇编+链接

翻译环境是如何将源代码转换为可执行的机器指令的呢?这需要深入了解翻译环境的工作流程。翻译环境主要由编译链接两大阶段组成,其中编译过程又可细分为三个步骤:预处理(部分资料称为预编译)、编译和汇编。

在C语言项目中,当多个.c文件需要一起构建时,生成可执行程序的过程分为两个主要步骤:

1、编译阶段:

  • 每个.c文件会单独进行编译处理,生成对应的目标文件
  • 注意:Windows环境下目标文件扩展名为.obj,Linux环境下为.o

2、链接阶段:

  • 所有目标文件将与链接库一起通过链接器处理
  • 最终生成可执行程序
  • 链接库包括运行时库(提供程序运行所需的基本函数)和第三方库

如果再把编译器展开成3个过程,那就变成了下面的过程(用gcc为例,拆解编译链接的过程):

2.1、预处理

在预处理阶段,源文件和头文件会被转换为以.i为后缀的中间文件。若要在gcc环境下查看test.c文件预处理后的结果,可执行以下命令:

cpp 复制代码
gcc -E test.c -o test.i

预处理阶段主要处理源文件中以#开头的预编译指令,例如#include、#define等,具体处理规则如下:

  1. 移除所有#define指令并展开宏定义
  2. 处理条件编译指令(#if、#ifdef、#elif、#else、#endif)
  3. 递归处理#include指令,将被包含的头文件内容插入到对应位置
  4. 删除所有注释内容
  5. 添加行号和文件名标识,便于编译器生成调试信息
  6. 保留#pragma指令供编译器后续使用

预处理生成的.i文件具有以下特点:

  • 不再包含宏定义(已全部展开)
  • 所有头文件内容已插入到相应位置
  • 可通过检查.i文件来验证宏定义和头文件包含的正确性

2.2、编译

编译过程对预处理后的文件执行一系列操作:包括词法分析、语法分析、语义分析及优化处理,最终生成对应的汇编代码文件。相关编译命令如下:

cpp 复制代码
gcc -S test.i -o test.s

编译以下代码时会发生什么情况?假设现有如下代码片段:

cpp 复制代码
 array[index] = (index+4)*(2+6);
2.2.1 词法分析:

源代码程序被输入扫描器后,扫描器执行词法分析任务,将代码中的字符序列分割成一系列标记(包括关键字、标识符、字面量和特殊字符等)。经词法分析后,上述程序共生成16个标记。

2.2.2 语法分析

接下来语法分析器,将对扫描产生的记号进行语法分析,从而产生语法树。这些语法树是以表达式为节点的树。

2.2.3 语义分析

由语义分析器来完成语义分析,即对表达式的语法层面分析。编译器所能做的分析是语义的静态分析。静态语义分析通常包括声明和类型的匹配,类型的转换等。这个阶段会报告错误的语法信息。

2.3、汇编

汇编器是将汇编代码转转变成机器可执行的指令,每⼀个汇编语句几乎都对应一条机器指令。就是根据汇编指令和机器指令的对照表⼀⼀的进行翻译,也不做指令优化。 汇编的命令如下:

cpp 复制代码
gcc -c test.s  -o test.o

2.4、链接

链接是将多个文件组合生成可执行程序的复杂过程。其主要步骤包括:地址与空间分配、符号决议以及重定位等。链接的核心作用是解决项目中多文件、多模块间的相互调用问题。比如: 在一个C的项目中有2个.c文件( test.c 和 add.c ),代码如下:

cpp 复制代码
//test.c
#define _CRT_SECURE_NO_WARNINGS 1

#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 c = 0;
	c = Add(a,b);
	printf("%d",c);
	return 0;
}
cpp 复制代码
//add.c
#define _CRT_SECURE_NO_WARNINGS 1
int g_val = 2022;
int Add(int x, int y)
{
	return x + y;
}

编译过程中,每个源文件都是独立编译生成对应的目标文件。例如:

  • test.c 编译生成 test.o
  • add.c 编译生成 add.o

当 test.c 文件中调用 add.c 中的 Add 函数和使用 g_val 变量时,由于文件是单独编译的,编译器在处理 test.c 时无法确定这些符号的实际地址。因此:

  1. 编译器会暂时记录调用 Add 函数的指令目标地址和引用 g_val 的地址
  2. 这些地址信息会被标记为待定状态

在最终链接阶段,链接器会:

  1. 根据符号表在其他模块中查找 Add 函数和 g_val 的实际地址
  2. 对 test.c 中所有引用 Add 和 g_val 的指令进行地址修正
  3. 将目标地址更新为正确的实际地址

这个地址修正的过程称为"重定位"。


3、运行环境

程序执行的基本流程如下:

  1. 内存加载阶段(程序必须载入内存中 ):

    • 在操作系统环境中,通常由操作系统自动完成程序加载
    • 在独立环境中,需要手动加载程序,或通过将可执行代码写入只读内存实现
  2. 程序启动阶段:

    • 程序开始执行后,首先调用main函数
    • 正式进入程序代码执行流程
  3. 运行时内存管理:

    • 程序使用运行时堆栈(stack)存储函数的局部变量和返回地址
    • 同时使用静态内存(static)存储变量,这些变量的值在整个程序执行期间保持不变
  4. 程序终止阶段:

    • 正常终止:通过main函数返回实现
    • 异常终止:程序意外终止的情况
相关推荐
Scott9999HH7 小时前
【IIoT流量实战】蒸汽管道阀门全关却仍有流量?用 Python 实现涡街信号 FFT 频谱分析与温压全补偿积算网关,深度拆解靠谱的涡街流量计厂家硬核技术标准
开发语言·python
码智社8 小时前
AES加密原理详解及Java实现加解密实战
java·开发语言
AI云海8 小时前
python 列表、元组、集合和字典
开发语言·python
萧瑟余晖9 小时前
JDK 26 新特性详解
java·开发语言
马优晨9 小时前
Freemarker 完整讲解(后端 Java 模板引擎)
java·开发语言·freemarker·freemarker 完整讲解·freemarker模板引擎
人邮异步社区11 小时前
怎么把C语言学到精通?
c语言·开发语言
心平气和量大福大11 小时前
C#-WPF-控件-TextBox 数据绑定
开发语言·c#·wpf
ttwuai12 小时前
Cursor 生成 CRUD 后,Go 后台接口别只测 200:JWT、RBAC 和 tenant_id 怎么验
开发语言·后端·golang
এ慕ོ冬℘゜12 小时前
前端基础:什么是时间戳?JS获取时间戳三种方法与实战用途
开发语言·前端·javascript
执明wa12 小时前
LayoutInflater详解: XML是如何变成View的?
android·xml·开发语言·android studio