2025-06-23 03:31:56C语言程序是如何运行起来的
当我们按下ctrl+f5时
一个程序运行起来需要经过翻译环境和可执行环境
翻译环境
也就是一个源文件到可执行文件这个阶段要做的事情,可以分成编译和链接两块,所谓集成开发环境也就是把这两个事情都做了
编译这个部分又可以分为三个部分,预编译,编译,汇编最后生成一个目标文件
链接最后把所有的目标文件链接起来组成一个可执行文件
详解:C语言程序的编译+链接
编译(编译器):
在Window平台不好观测从预处理到汇编的过程,所以,以Linus系统gcc编译器为演示
预处理(预编译)
输入:
gcc Filename.c -E > Filename.i
会在预编译之后停下来重定向进.i文件,生成一个预编译文件
预编译这个阶段把预处理的那些指令给完成,什么意思呢?具体包括
宏替换(Macro Replacement):预处理器会处理源代码中的宏定义,将宏名称替换为相应的宏定义内容。
文件包含(File Inclusion):处理#include预处理指令,将指定的头文件内容插入到源文件中。
条件编译(Conditional Compilation):处理条件编译指令(例如#if、#ifdef、#ifndef、#elif、#else、#endif),根据条件编译指令的真假来选择性地包含或排除部分代码。
行连接(Line Concatenation):将使用反斜杠\标记的多行代码连接为一行。
去除注释(Removing Comments):预处理器会删除源代码中的注释(/* */和//),使注释不会出现在编译后的代码中。
符号替换(Symbol Replacement):将预定义的符号(如__LINE__、__FILE__、__DATE__、__TIME__等)替换为其相应的值。
空行处理(Empty Line Handling):处理源代码中的空行,删除或忽略空行。
字符转义(Character Escaping):处理转义字符,如\"、\'、\n等。
详细请看预处理详解
编译
输入:
gcc Filename.i -S
将一个预处理文件编译,会生成一个.s的汇编文件
这个过程主要把C语言代码转换成汇编代码
进行的操作有,语法分析,词法分析,语义分析,符号汇总,前三个主要是把一门C语言翻译成汇编语言,就像中文翻译成英文那样,而符号汇总则是把作用域是全局的东西的名字汇总在一起,例如全局变量函数等
汇编
输入·:
gcc Filename.s -c
将一个汇编文件汇编,生成一个二进制目标文件,这个文件的格式是elf格式的,它里面把数据分为一个一个的数据段,和起来就是一个段表,可以用readelf工具读取这个文件。
这个过程主要把汇编语言转成机器语言,会生成一个符号表把符号汇总的哪些符号所象征的函数或变量的地址一 一对应对应起来,方便链接期间查找函数
链接:
首先一个可执行文件一般是由多个目标文件组成然后通过链接器把多个目标文件链接起来生成一个可执行文件,如图所示
其次会把多个源文件所产生的的段表,和符号表合并重定位为一个可执行文件
假设我们两个源文件test.c以及add.c
extern int Add(int x, int y);
int main()
{
int a = 10;
int b = 20;
int ret = Add(a, b);
return 0;
}
#define _CRT_SECURE_NO_WARNINGS 1
int Add(int x, int y)
{
return x + y;
}
段表合并:把相同数据段中的内容合并在一起,最后生成的可执行文件也是以这种数据段的形式存在
符号表合并:根据各自生成的符号表去查找符号,查找不到的说明不存在,去除无效符号,最后把查找到的结果合并成一个表,就可以给可执行文件使用,可执行文件可以根据这个符号表调用不同源文件的函数或全局变量
程序的执行环境则是可执行文件运行环境
载入内存中:
在有操作系统的环境中,程序的载入通常由操作系统完成。操作系统负责将程序从磁盘加载到内存中,以便执行。在独立的环境中,例如嵌入式系统,程序的载入可能需要手动安排,或者通过将可执行代码置入只读内存来完成。 程序的执行:
一旦程序载入到内存中,执行便开始。通常,操作系统或者硬件会将控制权转移给程序的入口点,即main函数。main函数是C程序的入口点,在这里程序的执行正式启动。 执行程序代码:
程序开始执行其代码,它使用运行时堆栈(stack)来存储函数的局部变量和返回地址。每当调用一个函数时,会在堆栈上创建一个新的帧来存储该函数的局部变量和其他相关信息。程序也可以使用静态(static)内存,静态内存中的变量在程序的整个执行过程中都保留其值。 终止程序:
程序的终止可以是正常的,也可以是意外的。正常情况下,main函数执行完毕,程序结束执行。在某些情况下,程序可能会因为出现错误或异常而意外终止,例如遇到未处理的异常或错误,或者调用了类似exit()函数来提前退出程序。