一个简单的 C 程序文件,经过 0、编写,1、预处理,2、编译,3、链接,终于生成了一个可执行文件

时间:2022-05-17 09:19:17
 用   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(集成开发环境),它包含了编辑器、编译器和调试器,通常还包含了项目管理和文档生成以及其他一系列辅助工具,可以大大简化项目开发周期。它们既不是编译器,也不是编辑器;它们包含了这些所有东西。