# 7.1 编译器驱动程序

考虑图 7-1 中的 C 语言程序。它将作为贯穿本章的一个小的运行示例，帮助我们说明关于链接是如何工作的一些重要知识点。

{% tabs %}
{% tab title="code/link/main.c" %}

```c
int sum(int *a, int n);

int array[2] = {1, 2};

int main()
{
    int val = sum(array, 2);
    return val;
}
```

{% endtab %}
{% endtabs %}

{% tabs %}
{% tab title="code/link/sum.c" %}

```c
int sum(int *a, int n)
{
    int i, s = 0;
    
    for (i = 0; i < n; i++) {
        s += a[i];
    }
    return s;
}
```

{% endtab %}
{% endtabs %}

> 图 7-1 示例程序 1。这个示例程序由两个源文件组成，main.c 和 sum.c。main 函数初始化一个整数数组，然后调用 sum 函数来对数组元素求和

大多数编译系统提供编译器驱动程序（compiler driver），它代表用户在需要时调用语言预处理器、编译器、汇编器和链接器。比如，要用 GNU 编译系统构造示例程序，我们就要通过在 shell 中输入下列命令来调用 GCC 驱动程序：

```c
linux> gcc -Og -o prog main.c sum.c
```

图 7-2 概括了驱动程序在将示例程序从 ASCII 码源文件翻译成可执行目标文件时的行为。（如果你想看看这些步骤，用 **-v** 选项来运行 GCC。）驱动程序首先运行 ✦C 预处理器（cpp）✦，它将 C 的源程序 main.c 翻译成一个 ASCII 码的中间文件 main.i：

```c
cpp [other arguments] main.c /tmp/main.i
```

{% hint style="info" %}
✦C 预处理器（cpp）✦：在某些 GCC 版本中，预处理器被集成到编译器驱动程序中。
{% endhint %}

![图 7-2 静态链接。链接器将可重定位目标文件组合起来，形成一个可执行目标文件 prog](https://4154149387-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MHt_spaxGgCbp2POnfq%2F-MI8jCXJzp_pYD5VGG8o%2F-MI8oSBe7kBcpszDCC7T%2F07-02%20%E9%9D%99%E6%80%81%E9%93%BE%E6%8E%A5.png?alt=media\&token=4a1bcea6-9bd1-4222-98e3-fc37075a2c56)

接下来，驱动程序运行 C 编译器（cc1），它将 main.i 翻译成一个 ASCII 汇编语言文件 main.s：&#x20;

```c
cc1 /tmp/main.i -Og [other arguments] -o /tmp/main.s
```

然后，驱动程序运行汇编器（as），它将 main.s 翻译成一个**可重定位目标文件**（relocatable object file）main.o：

```c
as [other arguments] -o /tmp/main.o /tmp/main.s
```

驱动程序经过相同的过程生成 sum.o。最后，它运行链接器程序 ld，将 main.o 和 sum.o 以及一些必要的系统目标文件组合起来，创建一个**可执行目标文件**（executable object file）prog：

```c
ld -o prog [system object files and args] /tmp/main.o /tmp/sum.o
```

要运行可执行文件 prog，我们在 Linux shell 的命令行上输入它的名字：

```c
linux> ./prog
```

shell 调用操作系统中一个叫做**加载器**（loader）的函数，它将可执行文件 prog 中的代码和数据复制到内存，然后将控制转移到这个程序的开头。
