菜鸟笔记
提升您的技术认知

gcc编译过程

阅读 : 66

一个编译过程包括下面4个阶段

  1. 预处理,预处理器CPP主要进行3个方面:文件包含、宏定义、条件编译;
  2. 编译,gcc将c文件编译成汇编文件;
  3. 汇编,as将汇编文件编译成机器码;
  4. 链接,ld将目标文件和外部符号进行链接,得到一个可执行二进制文件。

 下面以一个简单的test.c来探讨这个过程

#define NUMBER (1+2)

int main(void)
{
    int num = NUMBER;
    
    return 0;
}

1、预处理

预处理主要做下列的处理

  1. 将所有的#define删除,并且展开所有的宏定义 
  2. 处理所有的条件预编译指令,比如#if #ifdef #elif #else #endif等 
  3. 处理#include 预编译指令,将被包含的文件插入到该预编译指令的位置。 
  4. 删除所有注释 “//”和”/* */”. 
  5. 添加行号和文件标识,以便编译时产生调试用的行号及编译错误警告行号。 
  6. 保留所有的#pragma编译器指令,因为编译器需要使用它们 
[root@localhost test]# gcc -E test.c > test.i //等价于 cpp test.c >test.i  [root@localhost test]# cat test.i
# 1 "test.c"
# 1 ""
# 1 ""
# 1 "test.c"                           //使用gcc -E -P test.c > test.i 就不会显示上面4行#号开头的内容


int main(void)
{
 int num = (1+2);                      //这里进行了行宏替换 
 return 0;
}

 

 

2、编译

预处理后的代码生成汇编代码.

编译过程可分为6步:扫描(词法分析)、语法分析、语义分析、源代码优化、代码生成、目标代码优化。 

  1. 词法分析:扫描器(Scanner)将源代码的字符序列分割成一系列的记号(Token)。lex工具可实现词法扫描。 
  2. 语法分析:语法分析器将记号(Token)产生语法树(Syntax Tree)。yacc工具可实现语法分析。
  3. 语义分析:静态语义(在编译器可以确定的语义)、动态语义(只能在运行期才能确定的语义)。 
  4. 源代码优化:源代码优化器(Source Code Optimizer),将整个语法书转化为中间代码(Intermediate Code)(中间代码是与目标机器和运行环境无关的)。中间代码使得编译器被分为前端和后端。编译器前端负责产生机器无关的中间代码;编译器后端将中间代码转化为目标机器代码。 
  5. 目标代码生成:代码生成器(Code Generator). 
  6. 目标代码优化:目标代码优化器(Target Code Optimizer)。
[root@localhost test]# gcc -S test.i > test.s
[root@localhost test]# cat test.s
    .file    "test.c"
    .text
.globl main
    .type    main, @function
main:
    pushl    %ebp
    movl    %esp, %ebp
    subl    $16, %esp
    movl    $3, -4(%ebp)
    movl    $0, %eax
    leave
    ret
    .size    main, .-main
    .ident    "GCC: (GNU) 4.4.7 20120313 (Red Hat 4.4.7-4)"
    .section    .note.GNU-stack,"",@progbits

 

 

3、汇编

将汇编文件编译成机器码。

[root@localhost test]# gcc -c test.s -o test.o      //等价于 as test.s -o test.o

 

 

4、链接

通过调用链接器来链接程序运行需要的一大堆目标文件,以及所依赖的其它库文件,最后生成可执行文件。 

链接的主要内容是把各个模块之间相互引用的部分处理好,使得各个模块之间能够正确地衔接。 

链接的主要过程包括:地址和空间分配(Address and Storage Allocation),符号决议(Symbol Resolution),重定位(Relocation)  等。 

链接分为静态链接和动态链接。 
静态链接是指在编译阶段直接把静态库加入到可执行文件中去,这样可执行文件会比较大。 
而动态链接则是指链接阶段仅仅只加入一些描述信息,而程序执行时再从系统中把相应动态库加载到内存中去。

[root@localhost test]# gcc test.o -o test

 

 

GCC编译的详细过程

[root@localhost swap]# gcc -v test.c
Using built-in specs.
Target: i686-redhat-linux
Configured with: ../configure --prefix=/usr --mandir=/usr/share/man --infodir=/usr/share/info --with-bugurl=http://bugzilla.redhat.com/bugzilla --enable-bootstrap --enable-shared --enable-threads=posix --enable-checking=release --with-system-zlib --enable-__cxa_atexit --disable-libunwind-exceptions --enable-gnu-unique-object --enable-languages=c,c++,objc,obj-c++,java,fortran,ada --enable-java-awt=gtk --disable-dssi --with-java-home=/usr/lib/jvm/java-1.5.0-gcj-1.5.0.0/jre --enable-libgcj-multifile --enable-java-maintainer-mode --with-ecj-jar=/usr/share/java/eclipse-ecj.jar --disable-libjava-multilib --with-ppl --with-cloog --with-tune=generic --with-arch=i686 --build=i686-redhat-linux
Thread model: posix
gcc version 4.4.7 20120313 (Red Hat 4.4.7-4) (GCC) 
COLLECT_GCC_OPTIONS='-v' '-mtune=generic' '-march=i686'
 /usr/libexec/gcc/i686-redhat-linux/4.4.7/cc1 -quiet -v test.c -quiet -dumpbase test.c -mtune=generic -march=i686 -auxbase test -version -o /tmp/ccafFwbO.s
ignoring nonexistent directory "/usr/lib/gcc/i686-redhat-linux/4.4.7/include-fixed"
ignoring nonexistent directory "/usr/lib/gcc/i686-redhat-linux/4.4.7/../../../../i686-redhat-linux/include"
#include "..." search starts here: #include <...> search starts here:
 /usr/local/include
 /usr/lib/gcc/i686-redhat-linux/4.4.7/include
 /usr/include
End of search list.
GNU C (GCC) version 4.4.7 20120313 (Red Hat 4.4.7-4) (i686-redhat-linux)
    compiled by GNU C version 4.4.7 20120313 (Red Hat 4.4.7-4), GMP version 4.3.1, MPFR version 2.4.1.
GGC heuristics: --param ggc-min-expand=100 --param ggc-min-heapsize=131072
Compiler executable checksum: 5f02f32570d532de29ae0b402446343a
COLLECT_GCC_OPTIONS='-v' '-mtune=generic' '-march=i686'
 as -V -Qy -o /tmp/ccvfdR55.o /tmp/ccafFwbO.s
GNU assembler version 2.20.51.0.2 (i686-redhat-linux) using BFD version version 2.20.51.0.2-5.34.el6 20100205
COMPILER_PATH=/usr/libexec/gcc/i686-redhat-linux/4.4.7/:/usr/libexec/gcc/i686-redhat-linux/4.4.7/:/usr/libexec/gcc/i686-redhat-linux/:/usr/lib/gcc/i686-redhat-linux/4.4.7/:/usr/lib/gcc/i686-redhat-linux/:/usr/libexec/gcc/i686-redhat-linux/4.4.7/:/usr/libexec/gcc/i686-redhat-linux/:/usr/lib/gcc/i686-redhat-linux/4.4.7/:/usr/lib/gcc/i686-redhat-linux/
LIBRARY_PATH=/usr/lib/gcc/i686-redhat-linux/4.4.7/:/usr/lib/gcc/i686-redhat-linux/4.4.7/:/usr/lib/gcc/i686-redhat-linux/4.4.7/../../../:/lib/:/usr/lib/
COLLECT_GCC_OPTIONS='-v' '-mtune=generic' '-march=i686'
 /usr/libexec/gcc/i686-redhat-linux/4.4.7/collect2 --eh-frame-hdr --build-id -m elf_i386 --hash-style=gnu -dynamic-linker /lib/ld-linux.so.2 /usr/lib/gcc/i686-redhat-linux/4.4.7/../../../crt1.o /usr/lib/gcc/i686-redhat-linux/4.4.7/../../../crti.o /usr/lib/gcc/i686-redhat-linux/4.4.7/crtbegin.o -L/usr/lib/gcc/i686-redhat-linux/4.4.7 -L/usr/lib/gcc/i686-redhat-linux/4.4.7 -L/usr/lib/gcc/i686-redhat-linux/4.4.7/../../.. /tmp/ccvfdR55.o -lgcc --as-needed -lgcc_s --no-as-needed -lc -lgcc --as-needed -lgcc_s --no-as-needed /usr/lib/gcc/i686-redhat-linux/4.4.7/crtend.o /usr/lib/gcc/i686-redhat-linux/4.4.7/../../../crtn.o