0
点赞
收藏
分享

微信扫一扫

程序的编译与链接

河南妞 2022-04-30 阅读 74

思考

我们在VS创建一个test.c文件,输入以下内容:

int main()
{
	printf("hello world!!!");
	return 0;
}

在VS上运行这段代码,毫无疑问会在我们的控制台窗口打印hello world!!!在这段文字

那么大家有没有想过,这期间到底发生了什么?

难道仅仅就是:计算机阅读源代码,然后输出结果吗?

实际上,我们.c后缀的源代码首先会形成一个.exe后缀的可执行文件,可执行文件运行之后才会再相关的输出设备做出反馈。

上面这段代码,其实就是源代码中的printf经过翻译环境,形成了相应的二进制指令,这段二进制指令在CPU中运行,调用了系统调用api,在控制台窗口进行打印。

接下来我们就来详细分析一下,在翻译环境运行环境中到底做了什么。

翻译环境

接下来我们用一个模拟加减计算器的简单程序解释编译的过程:
add.c

extern int Add(int x, int y)
{
	return x + y;
}

sub.c

extern int Sub(int x, int y)
{
	return x - y;
}

test.c

extern int Add(int x, int y);//加法函数声明
extern int Sub(int x, int y);//减法函数声明
int main()
{
	int a = 1;
	int b = 2;
	int c = 0;
    int d = 0;
	c = Add(a, b);
    d = Sub(a, b);
	printf("%d", c);
}

整个翻译环境大致上可分为编译链接两部分。

在编译阶段,编译器会讲所有的源文件(.c格式)逐个转化成目标文件(.obj格式),

所有目标文件再加上链接库,经过链接器的链接,形成一个可执行程序

链接库:

test.c函数中我们使用了printf这个函数,但是我们并没有写,它的具体实现方式封装在了库里,如果想让最后的可执行程序正常运行,那必须把这部分文件也链接进去。

以下就是printf这个库函数对应的链接库,这些静态库都是以.LIB作为后缀的。

image-20220429200433172

编译

整个翻译环境可分为编译链接两个大阶段,而编译又可细分为三个阶段。

接下来,我们用linux的文件命名规则,为大家展示一下这三个阶段,和这三个阶段发生的操作。

预编译

这个阶段做一些文本替换,替换之后的test.i文件也是C语言的语法

  1. 头文件的包含(#include):用头文件的所有内容替换写#include<>的那一行
  2. 条件编译(#if):判断是否删除或保留#if和#endif中间的代码
  3. 删除注释:将注释掉的代码用一个空格替换
  4. 宏替换(#define定义符号的替换):将宏的文本内容替换到代码中

具体预处理指令,如”#define 、 #if 、 #include 、 #pragma……“,大家可以看一下这篇博客:点这里

编译

这个阶段是把C语言的代码转化成汇编代码,这里红框框里的就是翻译之后的汇编代码。

image-20220429210404425

在这个过程,做如下事情:

  1. 语法分析:编译错误就是在这个阶段发现的。
  2. 词法分析
  3. 语义分析
  4. 符号汇总

这里符号汇总就是把整个.i文件中出现的函数名汇总起来。

汇编

把汇编代码转换成二进制的指令,如果说一直到.s文件的汇编语言是我们肉眼能看懂的,那到了.o文件,就成了01组成的二进制指令,就只有CPU能看懂了。

汇编过程我们只区了解一个操作:

  1. 形成那个符号表。

是否还记得编译过程中的符号汇总,这里将给汇总的所有符号加一个地址,形成符号表:

image-20220429221108911

如果只有函数声明,那将其地址进行标记,全部置0.

链接

这个过程我们研究两件事:

  1. 合并段表
  2. 符号表的合并和重定位

合并段表

在生成.o文件的过程中,将文件分成了一个一个的,每个段有自己的作用,在链接阶段,把每个.o文件中相同作用的段进行合并。

image-20220429230114121

符号表的合并和重定位

把之前编译阶段生成的符号表进行合并,其中由于申明置的全0,现在把定义位置的地址赋过去。

在这个多文件链接的过程中,同时也会通过符号表查看来自外部的符号是否存在,平时我们遇到的链接错误就是在这里发现的。

image-20220429224444503

我们想想,如果在main函数中没有对Add、Sub进行声明,那么会出错吗?

答案是虽然有警告,但是依然能过。

就是因为即使没有声明,符号表合并之后也能找到它们的地址。

总结

运行环境

程序执行的过程:

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

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

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

  4. 终止程序。正常终止main函数;也有可能是意外终止。

举报

相关推荐

0 条评论