示例程序由两个源文件组成,main.c
和 swap.c
。
main函数初始化一个两元素的整数数组,然后调用swap函数来交换这一对数。
main.c
void swap();
int buf[2] = {1, 2};
int main()
{
swap();
return 0;
}
swap.c
extern int buf[];
int *bufp0 = &buf[0];
int *bufp1;
void swap()
{
int temp;
bufp1 = &buf[1];
temp = *bufp0;
*bufp0 = *bufp1;
*bufp1 = temp;
}
这是一种比较奇怪的交换两个数字的方式,但是它作为一个小的例子,来帮助说明关于链接是如何工作的一些重要知识点。
大多数编译系统提供 编译驱动程序(compiler driver),它代表用户在需要时调用语言预处理器、编译器、汇编器和链接器。【常用编译器可见博文常用编译器】
比如,要用 GNU 编译系统构造示例程序,就要通过在 shell 中输入下列命令行来调用 GCC 驱动程序:
# 关于编译参数的含义,后文会提到
gcc -O2 -g -o p main.c swap.c
下图概括了驱动程序在将示例程序从 ASCII码源文件翻译成可执行目标文件时的行为。(若想看这些步骤,用 -v
选项来运行GCC)。
详解步骤:
1、驱动程序首先运行 C 预处理器(cpp),将 C 的源程序 main.c
翻译成一个 ASCII 码的中间文件 main.i
:
cpp [other arguments] main.c /tmp/main.i
2、接下来,驱动程序运行 C 编译器(cc1),它将 main.i
翻译成一个 ASCII 汇编语言文件为 main.s
:
cc1 /tmp/main.i main.c -O2 [other arguments] -o /tmp/main.s
3、然后,驱动程序运行汇编器(as),它将 main.s
翻译成一个 可重定位目标文件 main.o
:
as [other arguments] -o /tmp/main.o /tmp/main.s
驱动程序经过相同的过程生成 swap.o
。
4、最后,它运行链接器程序 ld,将 main.o
和 swap.o
以及一些必要的系统目标文件组合起来,创建一个可执行的目标文件 p
:
ld -o p [system object files and args] /tmp/main.o /tmp/swap.o
5、要运行可执行文件,在Unix shel 的命令行上输入它的名字:
./p
shell 调用操作系统中一个叫做 加载器(loader)的函数,它拷贝可执行文件 p
中的代码和数据到存储器,然后将控制转移到这个程序的开头。