C 语言重拾【一】编程机制

目标代码文件、可执行文件和库

C 编程的基本策略是,用程序把源代码文件转换为可执行文件(其中包含可直接运行的机器语言代码)。

典型的 C 实现通过编译和链接两个步骤来完成这一过程。编译器把源代码转换成中间代码,链接器把中间代码和其他代码合并,生成可执行文件。C 使用这种分而治之的方法方便对程序进行模块化,可以独立编译单独的模块,稍后再用链接器合并已编译的模块。通过这种方式,如果只更改某个模块,不必因此重新编译其他模块。另外,链接器还将你编写的程序和预编译的库代码合并。

中间文件有多种形式。我们在这里描述的是最普遍的一种形式,即把源代码转专换为机器语言代码,并把结果放在目标代码文件(或简称目标文件)中(这里假设源代码只有一个文件)。虽然目标文件中包含机器语言代码,但是并不能直接运行该文件。因为目标文件中储存的是编译器翻译的源代码,这还不是一个完整的程序。

目标代码文件缺失启动代码(startup code)。启动代码充当着程序和操作系统之间的接口。例如,可以在 MS WindowsLinux 系统下运行 1BM PC 兼容机。这两种情况所使用的硬件相同,所以目标代码相同,但是 WindowsLinux 所需的启动代码不同,因为这些系统处理程序的方式不同。

1
2
3
4
5
6
7
#include <stdio.h>
int main (void)
{
printf ("Concrete contains gravel and cement. \n");
return 0;
}
// concrete.c

目标代码还缺少库函数。几乎所有的 C 程序都要使用 C 标准库中的两数。例如,concrete.c 中就使用了 printf() 函数。目标代码文件并不包含该函数的代码,它只包含了使用 printf() 函数的指令。printf() 函数真正的代码储存在另一个被称为库的文件中。库文件中有许多函数的目标代码。

链接器的作用是,把你编写的目标代码、系统的标准启动代码和库代码这 3 部分合并成一个文件,即可执行文件。对于库代码,链接器只会把程序中要用到的库函数代码提取出来。(如下图)

简而言之,目标文件和可执行文件都由机器语言指令组成的。然而,目标文件中只包含编译器为你编写的代码翻译的机器语言代码,可执行文件中还包含你编写的程序中使用的库函数和启动代码的机器代码。

在有些系统中,必须分别运行编译程序和链接程序,而在另一些系统中,编译器会自动启动链接器,用户只需给出编译命令即可。

GNU 编译器集合和 LLVM 项目

GNU 项目始于 1987 年,是一个开发大量免费 UNIX 软件的集合(GNU 的意思是 “GNU’s Not UNIX”,即 GNU 不是 UNIX)。GNU 编译器集合(也被称为GCC,其中包含GCC C编译器)是该项目的产品之一。GCC 在一个指导委员会的带领下,持续不断地开发,它的 C 编译器紧跟 C 标准的改动。GCC 有各种版本以适应不同的硬件平台和操作系统,包括 UNIX、Linux 和 Windows。用 gcc 命令便可调用 GCC C编译器。许多使用 gcc 的系统都用cc 作为gec的别名。

LLVM 项目成为 cc 的另一个替代品。该项目是与编译器相关的开源软件集合,始于伊利诺伊大学的 2000 份研究项目。它的 Clang 编译器处理 C 代码,可以通过 Clang 调用。有多种版本供不同的平台使用,包括 Linux。 2012年,Clang 成为 FreeBSD 的默认 C 编译器。Clang 也对最新的 C 标准支持得很好。

GNU 和 LLVM 都可以使用 -v 选项来显示版本信息,因此各系统都使用 cc 别名来代替 gcc 或 clang 命令。以下组合:

1
cc -v

显示你所使用的编译器及其版本。

gcc 和 clang 命令都可以根据不同的版本选样运行时选项来调用不同 C 标准。

1
2
3
gcc -std=c99 inform.c 
gcc -std=c1x inform.c
gcc -std=c11 inform.c

第 1 行调用 C99 标准,第 2 行调用 GCC 接受 C11 之前的草案标准,第 3 行调用 GCC 接受的 C11 标准版本。Clang 编译器在这一点上用法与 GCC 相同。

参考文献

  • C Primer Plus