由源文件到可执行文件

零.前言

本文主要讲述由.c文件变成.out文件的过程中每个部分的重要内容,并不是所有内容,只是粗略了解这部分的流程,详细的部分还需要学完一些像编译原理的课什么之类的才能更加全面深入地了解(博主水平有限。。)。

1.程序的翻译环境与执行环境

1.引入

我们在编写程序的时候,写入的是一个.c文件,最后执行出来的是.exe文件,本文粗略讨论的就是这一过程。

2.翻译环境与执行环境

任何一个程序在由.c文件转换成.exe文件的过程中都需要经过两个环境,即翻译环境与执行环境。

1.翻译环境:

在这个环境中源代码被转换为可执行的机器指令。

2.执行环境

它用于实际执行代码。

3.过程图解

 这个就是.c文件转换成.exe文件的主要流程。一个.c程序经过

预处理-->编译-->汇编-->链接-->运行   这一过程产生了.exe文件。

在Windows系统下我们可以再画一张图来解释:

 

这张图就是Windows系统下的由.c文件生成.exe文件的过程。其中的编译器就是上图中编译过程所用到的工具。

因为VS是在Windows系统下的集成开发环境,并不容易观察详细的过程,所以下面我们用linux系统来举例说明。方便观察到每一步的细节。

linux与windows不同的是中间文件的后缀不同,我们接下来都会使用linux环境下的后缀。

2.举例程序

我们首先选两个源文件作为例子:

#include<stdio.h>
extern int add(int a, int b);
int main()
{
	int a = 10;
	int b = 20;
	int ret = add(a, b);
	printf("%d", ret);
	return 0;
}
int add(int a, int b)
{
	return a + b;
}

上面两个程序为两个.c文件分别包含的内容。

我们可以先运行一下(强迫症)看一下打印的效果:

 3.预处理(预编译)

在linux系统下预处理的指令是-E即预处理结束后程序停下来。预处理之后.c文件变成.i文件。

1.#定义的部分

1.#include<stdio.h>:

包含文件。即在预处理阶段已经将#include包含的所有内容包含在文件中了。

 2. #define MAX 10:

定义变量,比如我们定义一个MAX

#define MAX 10;
#include<stdio.h>
int main()
{
int a=MAX;
int b=10;
return 0;
}

在预处理过后,.i文件存储的是int a=10而不是int a=MAX了,说明预处理已经将#define的内容实现完了。

  还有#pragma等都是在预处理阶段来实现的。

这里是为了走一个流程,一会儿会详解预处理部分。

2.删除注释

还有注释也会被删除,假如我们写的代码中有注释,在预处理之后的.i文件中所有注释都会被删除。

4.编译

在linux系统下编译的指令是-S即程序执行到编译的时候停下来,编译后.i文件变成.s文件。

编译要经过的过程

编译的主要内容就是将C语言代码转化成汇编代码。

其中要经过

1.语法分析

2.词法分析

3.语义分析

4.符号汇总

符号汇总

符号汇总汇总的是全局符号,用上述程序举例:

add.c文件中汇总的就是add,test.c文件中汇总的符号就是add和main,里面的函数或者变量都不是全局范围的所以不需要汇总。

5.汇编

在linux系统下指令为-c,.s文件生成.o文件。

汇编的过程

将汇编代码转化成二进制指令(机器指令)

形成符号表

在编译过程中产生的符号表会被分配地址。

在add.c中汇总出来的符号是add,汇编的过程就是为add分配一个地址,假设是0x100

在test.c中汇总出来的符号是main和add,虽然add只是一个声明没有实际的意义,但也会得到一个地址,假设add得到的地址时0x000,main得到的地址时0x400。

6.链接

链接分为两个步骤:合并段表和符号表的合并与重新定位。

即将.o文件中的段表和符号表进行合并,在合并符号表的过程中会将符号表中无意义的地址删去,比如.o文件中add.c得到的那个符号表中的地址为0x100是有意义的,但是test.c文件中只是对add进行声明,并没有实际意义,所以就会将0x000删除,使得符号表中只有add.c中add的地址和main的地址。

在linux系统下,生成的.o文件是一个二进制文件,我们使用redelf来读取linux中二进制文件。

7.运行环境

1.程序必须载入到内存中。在具有操作系统的环境中,一般由操作系统完成。在独立的环境中,程序的载入必须由手工安排,也可能是通过可执行代码置入只读内存来完成。

2.程序的执行便开始。接着便调用main函数。

3.开始执行程序代码,这个时候程序将使用一个运行时堆栈(函数栈帧),存储函数的局部变量和返回地址。程序同时也可以使用静态内存,存储于静态内存中的变量在程序的整个执行过程中一直保留它们的值。

4.终止程序。正常终止main函数;也有可能导致意外发生。

8.总结

本文只是粗略的讲述了从.c文件到.out文件的一些重要过程,其实每一个部分都有大量的知识需要进行推敲,博主都会在以后的博文中涉及到,对这些流程感兴趣的可以去阅读一本书《程序员的自我修养》里面有对这部分的详细阐述。

posted @ 2022-09-24 20:29  卖寂寞的小男孩  阅读(110)  评论(0)    收藏  举报