C语言程序执行过程之链接
一个C语言程序从开始到结束都经历了些什么?
恩,这个问题看起来是很简单了,但是却也不那么简单其实,想深入了解吗,跟着我燥起来吧.
先上图吧
一个程序从开始到结束一共经历了四个阶段,就从hello world!
开始说吧。
#include <stdio.h>
int main()
{
printf("hello world!");
}
- 预处理: 预处理器根据以字符
#
开头的命令,修改原始C程序。将所写头文件中的内容,添加到源程序文本中来。还有一点我要强调一下,头文件中只有函数的声明,并没有函数实际内容,那既然这样,函数本身是放在哪儿呀?那就继续向下看喽。哦哦,差一点忘了,预处理阶段还有注释和宏处理。预处理的结果是得另一个C程序 hello.i. 在上面这个程序中,#include <stdio.h>
中的内容被加载进源文件。 - 编译:编译器将文本文件hello.i翻译成文本文件hello.s,它包含一个汇编语言程序。
- 汇编:汇编器(as)将hello.s翻译成机器语言指令,把这些指令打包成一种可重定位目标程序的格式,并将结果保存在目标文件hello.o中。
- 链接阶段:请注意,hello程序调用了printf函数,那么这个函数原型在哪,我一度认为在#include <stdio.h>,傻逼吧,事实上,头文件中只有函数声明,那么他的函数原型到底在哪呀??这就很让人困惑了,在linux 运行
gcc -v hello.c
,其中会有下面这一段,这是个啥???稍等,马上解释。
如果你写过线程用过MySQL库……你就会知道要编译这个程序,你后面必须动态链接库,这是编译用MySQL 接口函数写的一个程序,如果后面不加动态链接库的参数,就回显示未定义的应用,是因为程序中有#include <mysql/mysql.h>
头文件,函数有定义,但是并没有函数实体。
好了,我觉得现在应该有点明白了吧,是因为想我们常用的函数他是编译器自己为我们链接的,是隐式链接在我们的程序中的。
好,到这我们基本梳理完了C语言的整个编译执行过程,那我们就说完了吗,当然不可能。上面说的是单文件,C语言只能单文件编译吗,当然不是啊,大型的应用程序都是由多文件构成的。
好,终于到了今天的主角登场了,今天其实主要是想说一下多文件的链接阶段的。
链接是将各种代码和数据片段收集并组合成为一个单一文件的过程,这个文件可被加载(复制)到内存并执行。 在现代系统中,链接是由链接器的程序自动执行的。链接器使得分离编译成为可能。我们不用将一个大型的应用程序组织为一个巨大的源文件,而是可以把它分解为更小,更好管理的模块,可以独立编译修改和编译这些模块。当我们改变这些模块的中的一个时,只需要简单的编译它,并重新链接应用,而不必重新编译其他文件。
int sum(int *a, int n);
int array[2] = {1,2};
int main()
{
int val = sum(array, 2);
return val;
}
int sum(int *a, int n)
{
int i, s = 0;
for (i =0; i<n; i++) {
s += a[i];
}
return s;
}
这两个程序作为示例程序,帮助我们说明关于链接是如何工作的一些重要知识点。
这张图就很清楚了,概括了驱动程序将示例程序从ASCII码源文件翻译成可执行目标文件时的行为,(如果你想看看这些步骤,用-v
选项来运行GCC)在LINUX下用GCC编译gcc main.c sum.c -o prog
看起来很简单是把,和函数调用的原理差不多,好像也没有什么必要。但是多文件在大型应用程序的开发中,其实这都没啥好说的,可是等到你学会了封装,那是真的爽,代码可复用,留好接口函数。也许下一个zzy 库就是你写的。
接下来我们简单说说静态链接和动态链接:
静态链接: 像Linux LD 程序这样的静态链接器以一组可重定位目标文件和命令行参数作为输入,生成一个完全链接的,可以加载和运行的可执行目标文件作为输出。
动态链接:
- 静态库解决了许多关于如何让大量相关函数对应用程序可用的问题。然而,静态库仍然有一些明显的缺点。静态库和所有的软件一样,需要定期维护更新,他们必须以某种方式了解到该库的更新情况。然后显示地将他们的程序与更新的库重新链接。另一个问题是几乎每个C程序都是用标准I/O函数,比如printf 和scanf 。在运行时,这些代码会被复制到每个运行程序的文本段中。这是对内存资源的极大浪费。
- 共享库是致力于解决静态库缺陷的一个现代创新产物。**共享库是一个目标模块,再运行或者加载时,可以加载到任意的内存地址,并和一个在内存中的程序链接起来。**这个过程叫做动态链接。
共享库是以两种不同的方式来共享的。首先,在任何给定的文件系统中,对于一个库只有一个.so文件的。所以引用该库的可执行目标文件共享.so 文件中的代码和数据,而不是想静态库的内容那样被复制嵌入到引用他们的可执行的文件当中。其次,在内存中,一个共享库的.text节的副本可以被不同的正在运行的进程共享。