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函数返回实现
    • 异常终止:程序意外终止的情况
相关推荐
半夏知半秋8 小时前
mongodb的复制集整理
服务器·开发语言·数据库·后端·学习·mongodb
一点七加一8 小时前
Harmony鸿蒙开发0基础入门到精通Day09--JavaScript篇
开发语言·javascript·ecmascript
nvd118 小时前
python异步编程 -协程的实际意义
开发语言·python
liebe1*18 小时前
C语言程序代码(四)
c语言·数据结构·算法
沐知全栈开发9 小时前
NumPy 统计函数
开发语言
chao18984410 小时前
C 文件操作全解速览
服务器·c语言·c#
青光键主10 小时前
C语言内功强化之const修饰指针
c语言·开发语言
骷大人10 小时前
php安装skywalking_agent
开发语言·php·skywalking
恋恋西风11 小时前
Qt 打开文件列表选择文件,实现拖拽方式打开文件,拖拽加载
开发语言·qt