用 C 写一个程序需要些什么工具?某甲:“编译器,VC 啦 TC 啦什么的……”
是吧?不对:)其实这句话首先不完整,其次有逻辑的错误。我们需要的不仅仅是一个编译器;首先我们需要的是一个书写程序的工具,一般统称“编辑器”。最简单的编辑器就是 Windows 自带的写字板,好一点的有 UltraEdit 或者 EditPlus 之类。在 Linux 下可以使用 VI, Vim 或者大名鼎鼎的 Emacs 作为编辑器。编辑器的作用在于让你输入程序,并且保存为一个普通的文本文档。因此,如果你能记得在保存的时候选择“存为文本文件(*.txt)”或者类似的命令(以保证得到的文件里面没有杂七杂八的格式信息),用 Microsoft Word 或者 MacroMeidia DreamWeaver MX 也没有问题^_^ C/C++ 的源文件名没有 Java 的文件命名机制那么 BT,但是有几个常规如下:
·C 源码文件后缀名为 .c
·C++ 源码文件后缀名为 .cpp .cxx .cc,或者在区分文件名大小写的系统上为 .C
·C 头文件(表头档——我喜欢这个名字)为 .h
·C++ 头文件为 .h 或者 .hpp,而标准库的头文件通常没有后缀名(如 iostream)
所有的这些后缀名都不影响文件本身的内容:所有文件都是 plain text ——纯文本文档。后缀名的作用在于提示程序员它所包含的内容,同时可以提示编译器应该采取的行为。
写程序不是写小说。除了编辑器,我们还需要一套工具,把我们写的程序代码转换成机器可以执行的二进制格式。这一套工具应该至少包含一个预处理器,一个编译器和一个链接器。对于 GNU Binutils/GCC 系列工具,有预处理器 cpp (C Pre-Processor),编译器 gcc(GNU C Compiler)和 g++ (GNU C++ Compiler),汇编器 as(The GNU assembler) 和链接器 ld(The GNU Linker )。这么多工具,都是干什么的?让我们一个一个瞧瞧看。
·这是一个很经典的 C 程序,传说中的 Hello, World
gentoo@yuantoo tmp $ cat hello.c
#include <stdio.h>
int main()
{
printf( "Hello, world!\n ");
}
·编译成 hello.exe
gentoo@yuantoo tmp $ gcc hello.c -o hello.exe
·运行
gentoo@yuantoo tmp $ ./hello.exe
Hello, world!
好,现在看看它到底都做了什么工作。
第二步中,我用 gcc hello.c -o hello.exe 把 hello.c 编译成了 hello.exe。这里其实还有几个步骤,但是 Gcc 自动的完成了它们。这包括预处理、编译和链接。
首先是预处理。我们可以让 gcc 在预处理之后停下:
gentoo@yuantoo tmp $ gcc -E hello.c
结果是,gcc 在屏幕上飞快的打印了无数的看不见的信息。可以看到最后几行是这样的:
extern char *ctermid (char *__s) ;
# 807 "/usr/include/stdio.h " 3 4
extern void flockfile (FILE *__stream) ;
extern int ftrylockfile (FILE *__stream) ;
extern void funlockfile (FILE *__stream) ;
# 831 "/usr/include/stdio.h " 3 4
# 2 "hello.c " 2
int main()
{
printf( "Hello, world!\n ");
}
也就是说,我们的 hello.c 的内容是在最后。而前面的那些东西,都是从 <stdio.h> 以及 stdio.h 的包含文件中插入进来的信息。预处理器主要的工作就是处理所有源码中以 # 开头的行,将 #include 指令替换成指令指出的文件的内容,对 #define 定义的符号进行了文本替换,以及根据符号选择需要进入结果文件的内容。我们短短四行字的代码文件,经过 gcc 的预处理,得到了一个 913 行的文件。
预处理之后的工作是汇编——这也是真正编译工作的第一步骤。用 -S 标志可以让 gcc 在汇编之后停下来。
gentoo@yuantoo tmp $ gcc -S hello.c
gentoo@yuantoo tmp $ ls
hello.c hello.s
我们得到了一个名为 hello.s 的文件。看看它的开始部分
gentoo@yuantoo tmp $ head -n10 hello.s
.file "hello.c "
.section .rodata
.LC0:
.string "Hello, world!\n "
.text
.globl main
.type main, @function
main:
pushl %ebp
movl %esp, %ebp
熟悉吧!都是生成的汇编码。
生成汇编码之后的步骤是汇编,把汇编码转换成对象文件。
gentoo@yuantoo tmp $ as hello.s -o hello.o
得到了 hello.o,就是包含 hello.c 代码的对象代码。
得到的 hello 还不能运行,我们需要把它和 C 语言运行库链接起来。它不仅包含了程序的入口,还有 printf 等标准 C 库函数的实现。
gentoo@yuantoo tmp $ ld -o a.out -dynamic-linker /lib/ld-linux.so.2 /usr/lib/crt?.o hello.o -lc
这里,-o hello.exe 表示输出为 hello.exe 文件。-dynamic-linker /lib/ld-linux.so.2 表示将程序动态链接到 /lib/ld-linux.so.2 这个 shared object。这个 shared object 作为操作系统中的一个特殊的库,它负责引入其他的 so。同时,我们要在我们的程序中包含 /usr/lib 下的几个 crt?.o 对象文件:它们中包含了 C 程序的所需要的运行时环境。最后一个参数 -lc 表示将程序链接到标准 C 库上(名为 libc.so.5,在 /usr/lib 目录下。ld 会根据配置文件自动搜索 /usr/lib 目录;如果库文件在其他目录中,则需要用 -L 参数指出。)执行完毕,我们得到了一个具有执行权限的 hello.exe 文件。
gentoo@yuantoo tmp $ ./hello.exe
Hello, world!
gentoo@yuantoo tmp $
就这样,一个简单的 C 程序文件,经过 0、编写,1、预处理,2、编译,3、链接,终于生成了一个可执行文件。一般而言,gcc 编译器可以替我们完成整个过程,只要简简单单一个 gcc -o hello.exe hello.c 命令,三个步骤就可以统统完成。
从上面可以看出,编写代码和编译代码完全是分离的两个过程,可以用完全不同的工具替换每个步骤(譬如用 notepad 或者 EditPlus 作编辑器编写代码,用 Cygwin 或者 Mingw 做编译器,等等)。VC 和 TC 都是所谓的 IDE(集成开发环境),它包含了编辑器、编译器和调试器,通常还包含了项目管理和文档生成以及其他一系列辅助工具,可以大大简化项目开发周期。它们既不是编译器,也不是编辑器;它们包含了这些所有东西。