C源程序完整编译过程

下面总结一下编译的完整过程:
C源程序-->预编译处理(.c)-->编译、优化程序(.s、.asm)-->汇编程序(.obj、.o、.a、.ko)-->链接程序(.exe、.elf、.axf等)。


一、预编译处理(cpp)
它主要包括四个过程
    1.宏定义指令,如#define N 6,#undef等
        对于前一个伪指令,预编译所要做的是将程序中的所有N用6替换,请大家注意这里是替换,并不是像作为函数参数那样将6复制进N这个变量。对于后者,则将取消对某个宏的定义,使以后出现的N不再被替换。
    2.条件编译指令,如#ifdef,#ifndef,#endif等。
        这些伪指令的引入使得程序员可以通过定义不同的宏来决定编译程序对哪些代码进行处理。预编译程序将根据有关的文件,将那些不必要的代码过滤掉。这样就能在编译阶段减少编译时间,提高效率,看看这是多好的指令。O(∩_∩)O~
    3.头文件包含指令,如#include "file.h"或#include 等。
        在头文件中一般用伪指令#define定义了大量的宏(最常见的是字符常量),同时包含有各种外部符号的声明。
        采用这样的做法一来可以让我们直接调用一些复杂库函数;二来可以免去我们在写程序时重复做一些定义声明工作的麻烦。试想一下,一旦我们写好头文件,那么以后要用到相关模块就再也不用写这些函数了,直接#include 就OK了,这可是一劳永逸啊,天大的便宜呢,呵呵。
    这里顺便提一下#include<>与#include“”的区别。
         #include<>:这条指令就是告诉编译器去系统默认的路径寻找相关文件。
         #include”” :这条是告诉编译器先去源程序所在目录下寻找,如果没有就去系统默认路径寻找。
    4.特殊符号,预编译程序可以识别一些特殊的符号。
         例如在源程序中出现的LINE标识将被解释为当前行号(十进制数),FILE则被解释为当前被编译的C源程序的名称。预编译程序就是对在源程序中出现的这些特殊符号将用合适的值进行替换。
    预编译阶段基本上是完成对源程序的相关代码进行替换,这样之后程序的原意没有改变,就是代码的内容有所不同,这样为以后的编译做好准备
    通常使用以下命令来进行预处理:

        gcc -E hello.c -o hello.i


二、编译、优化程序(gcc/g++)

    编译过程就是把预处理完的文件进行一系列的词法分析,语法分析,语义分析及优化后生成相应的汇编代码。
        $gcc –S hello.i –o hello.s
    注:现在版本的GCC把预处理和编译两个步骤合成一个步骤,用cc1工具来完成。gcc其实是后台程序的一些包装,根据不同参数去调用其他的实际处理程序,比如:预编译编译程序cc1、汇编器as、连接器ld
    可以看到编译后的汇编代码(hello.s)如下:
  1     .file   "c.c"
  2     .section    .rodata   
  3 .LC0:
  4     .string "hello world" 
  5     .text
  6     .globl  main          
  7     .type   main, @function
  8 main:
  9 .LFB0:
 10     .cfi_startproc
 11     pushq   %rbp
 12     .cfi_def_cfa_offset 16
 13     .cfi_offset 6, -16
 14     movq    %rsp, %rbp
 15     .cfi_def_cfa_register 6
 16     movl    $.LC0, %edi
 17     call    puts
 18     popq    %rbp
 19     .cfi_def_cfa 7, 8
 20     ret
 21     .cfi_endproc
 22 .LFE0:
 23     .size   main, .-main
 24     .ident  "GCC: (Ubuntu/Linaro 4.8.1-10ubuntu9) 4.8.1"
 25     .section    .note.GNU-stack,"",@progbits       
三、汇编程序(as)
    在这个阶段是将汇编代码翻译成目标文件,这时的文件已经是二进制代码了。在windows环境下文件的后缀名是.obj;而在unix下则有是o、.a、.ko等文件。
    目标文件由段组成。通常一个目标文件中至少有两个段:
        代码段:该段中所包含的主要是程序的指令。该段一般是可读和可执行的,但一般却不可写。
        数据段:主要存放程序中要用到的各种全局变量或静态的数据。一般数据段都是可读,可写,可执行的。
    通常使用以下命令来进行汇编:
gcc –c hello.c –o hello.o


四、链接程序(ld)

     也许有人会有疑问,上面的目标代码已经是机器码了,也就是说CPU可以识别这些文件了,那为什么我们还要链接程序呢?对!那些被包含的头文件,以及当我们的程序分布于很多源文件时,那么这些源文件该怎么处理呢,这就是连接器的作用,它们被翻译成目标代码后需要被链接到一起才能被执行。这样就ok了!
    谈到函数库的链接,我们还需要了解点函数库的知识,函数库分静态链接库(又称静态库*.lib)和链接动态库(又称动态库*.dll)。

    静态库的链接在编译时会被编译进汇编文件,这样的操作会改变文件大小;而动态库则是在执行时(双击运行),当需要动态库中的文件时才被链接到可执行文件的。


你可能感兴趣的:(Linux)