大家都知道编译的四个步骤:预处理、编译、汇编链接。但是这些步骤都是由谁完成的呢,今天做了几个实验验证一下。
(本人对编译和链接的原理不是很精通,以下内容是摸索的,难免出错。不要被我误导了)
首先编写一个简单的C文件,比如"hello, world!",然后使用-v选项来编译它,这样它的编译步骤就可以显示出来了。
[zlg@localhost gdbtest]$ gcc -v //显示gcc的版本信息
Using built-in specs.
Target: i386-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-languages=c,c++,objc,obj-c++,java,fortran,ada --enable-java-awt=gtk --disable-dssi --enable-plugin --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-cpu=generic --build=i386-redhat-linux
Thread model: posix
gcc version 4.3.2 20081105 (Red Hat 4.3.2-7) (GCC)
[zlg@localhost gdbtest]$ gcc -v main.c //使用-v选项编译main.c
Using built-in specs.
Target: i386-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-languages=c,c++,objc,obj-c++,java,fortran,ada --enable-java-awt=gtk --disable-dssi --enable-plugin --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-cpu=generic --build=i386-redhat-linux
Thread model: posix
gcc version 4.3.2 20081105 (Red Hat 4.3.2-7) (GCC)
COLLECT_GCC_OPTIONS='-v' '-mtune=generic'
/usr/libexec/gcc/i386-redhat-linux/4.3.2/cc1 -quiet -v main.c -quiet -dumpbase main.c -mtune=generic -auxbase main -version -o /tmp/ccCwWqS3.s /*生成临时的汇编文件*/
ignoring nonexistent directory "/usr/lib/gcc/i386-redhat-linux/4.3.2/include-fixed"
ignoring nonexistent directory "/usr/lib/gcc/i386-redhat-linux/4.3.2/../../../../i386-redhat-linux/include"
#include "..." search starts here:
#include <...> search starts here: /*头文件的搜索目录*/
/usr/local/include
/usr/lib/gcc/i386-redhat-linux/4.3.2/include
/usr/include
End of search list.
GNU C (GCC) version 4.3.2 20081105 (Red Hat 4.3.2-7) (i386-redhat-linux)
compiled by GNU C version 4.3.2 20081105 (Red Hat 4.3.2-7), GMP version 4.2.2, MPFR version 2.3.2.
GGC heuristics: --param ggc-min-expand=59 --param ggc-min-heapsize=55617
Compiler executable checksum: 3bee52601079f736b7b63b762646f4ba
COLLECT_GCC_OPTIONS='-v' '-mtune=generic'
as -V -Qy -o /tmp/cca2z171.o /tmp/ccCwWqS3.s /*调用汇编器生成目标代码*/
GNU assembler version 2.18.50.0.9 (i386-redhat-linux) using BFD version version 2.18.50.0.9-7.fc10 20080822 /*汇编器版本信息*/
COMPILER_PATH=/usr/libexec/gcc/i386-redhat-linux/4.3.2/:/usr/libexec/gcc/i386-redhat-linux/4.3.2/:/usr/libexec/gcc/i386-redhat-linux/:/usr/lib/gcc/i386-redhat-linux/4.3.2/:/usr/lib/gcc/i386-redhat-linux/:/usr/libexec/gcc/i386-redhat-linux/4.3.2/:/usr/libexec/gcc/i386-redhat-linux/:/usr/lib/gcc/i386-redhat-linux/4.3.2/:/usr/lib/gcc/i386-redhat-linux/
LIBRARY_PATH=/usr/lib/gcc/i386-redhat-linux/4.3.2/:/usr/lib/gcc/i386-redhat-linux/4.3.2/:/usr/lib/gcc/i386-redhat-linux/4.3.2/../../../:/lib/:/usr/lib/
COLLECT_GCC_OPTIONS='-v' '-mtune=generic'
/*collect2完成最后的链接工作*/
/usr/libexec/gcc/i386-redhat-linux/4.3.2/collect2 --eh-frame-hdr --build-id -m elf_i386 --hash-style=gnu -dynamic-linker /lib/ld-linux.so.2 /usr/lib/gcc/i386-redhat-linux/4.3.2/../../../crt1.o /usr/lib/gcc/i386-redhat-linux/4.3.2/../../../crti.o /usr/lib/gcc/i386-redhat-linux/4.3.2/crtbegin.o -L/usr/lib/gcc/i386-redhat-linux/4.3.2 -L/usr/lib/gcc/i386-redhat-linux/4.3.2 -L/usr/lib/gcc/i386-redhat-linux/4.3.2/../../.. /tmp/cca2z171.o -lgcc --as-needed -lgcc_s --no-as-needed -lc -lgcc --as-needed -lgcc_s --no-as-needed /usr/lib/gcc/i386-redhat-linux/4.3.2/crtend.o /usr/lib/gcc/i386-redhat-linux/4.3.2/../../../crtn.o
总结一下编译过程:
启动/usr/bin/gcc
/usr/libexec/gcc/i386-redhat-linux/4.3.2/cc1 进行预处理和编译,生成的临时汇编文件放在/tmp
/usr/bin/as 将上一步的汇编文件生成临时目标文件放在/tmp
/usr/libexec/gcc/i386-redhat-linux/4.3.2/collect2 调用连接器/usr/bin/ld生成可执行文件
注意,没有一个单独的预处理工具,预处理和编译的工作由ccl完成。ccl生成的临时汇编文件在/tmp下:/tmp/ccCwWqS3.s
as是汇编器,负责生成目标文件。目标文件依然作为临时文件存放在/tmp下面: /tmp/cca2z171.o
最后调用collect2负责链接的工作。collect2针对C++程序进行特殊处理。
collect2的信息参考:
gnu官方collect2的文档:http://gcc.gnu.org/onlinedocs/gccint/Collect2.html
别人的博客:http://hellogcc.blogbus.com/logs/67096640.html /// http://alpha-blog.wanglianghome.org/2011/04/14/collect2/
对于gcc/ccl/collect2等关系的描述参考:《gcc的组件和软件工具》http://hi.baidu.com/xuelicheng/blog/item/cd5650505ecaea658535241a.html
gcc/as/ld的相关知识参考:http://www.cppblog.com/jinglexy/archive/2007/04/19/22298.html 。
知识点补充:
[zlg@localhost gdbtest]$ ls /usr/libexec/gcc/i386-redhat-linux/4.3.2/
cc1 cc1plus collect2 f951
[zlg@localhost gdbtest]$
实验1:验证collect2会调用链接器ld.
[zlg@localhost gdbtest]$ /usr/libexec/gcc/i386-redhat-linux/4.3.2/collect2 -v
collect2 version 4.3.2 20081105 (Red Hat 4.3.2-7) (i386 Linux/ELF)
/usr/bin/ld -v
GNU ld version 2.18.50.0.9-7.fc10 20080822
[zlg@localhost gdbtest]$
实验2:CPP的实现依赖于cc1
在http://gcc.gnu.org/projects/cpplib.html
有这么一段话:
这段话的意思是CPP(也就是C语言预处理器)不再独立存在,已经被合并进了cpplib。现在由编译器前端工具负责调用。
在Linux系统(fedora10)上可用的CPP有两个,/lib/cpp和/usr/bin/cpp,其中前者是后者的符号链接。将/usr/bin/cpp改为别的名字,GCC照常编译,这说明/usr/bin/cpp并不会被GCC调用 。但是经过验证,“/usr/bin/cpp main.c”的确可以对C文件做预处理。
接下来再做实验:
[root@localhost gdbtest]# /usr/bin/cpp -v
Using built-in specs.
Target: i386-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-languages=c,c++,objc,obj-c++,java,fortran,ada --enable-java-awt=gtk --disable-dssi --enable-plugin --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-cpu=generic --build=i386-redhat-linux
Thread model: posix
gcc version 4.3.2 20081105 (Red Hat 4.3.2-7) (GCC)
COLLECT_GCC_OPTIONS='-E' '-v' '-mtune=generic'
/usr/libexec/gcc/i386-redhat-linux/4.3.2/ cc1 -E -quiet -v - -mtune=generic
ignoring nonexistent directory "/usr/lib/gcc/i386-redhat-linux/4.3.2/include-fixed"
ignoring nonexistent directory "/usr/lib/gcc/i386-redhat-linux/4.3.2/../../../../i386-redhat-linux/include"
#include "..." search starts here:
#include <...> search starts here:
/usr/local/include
/usr/lib/gcc/i386-redhat-linux/4.3.2/include
/usr/include
End of search list.
可以看到,向cpp传递-v选项,竟然会启动cc1,这说明cpp调用了cc1。最重要的是,向cpp传入-v选项,cpp给cc1的参数中竟然有-E选项,这说明cc1才是预处理的主体,cpp的默认动作就是将-E参数传给cc1,它自己啥也做不了。
以前cpp是一个独立的预处理器,现在cpp仅仅是一个依赖于cc1的封装接口 。cc1才是做预处理的地方。
实验3:验证预处理和编译都是由cc1完成的。并且预处理不生成临时文件
[zlg@localhost gdbtest]$ gcc -v -E main.c > main.i /*仅仅进行预处理*/
Using built-in specs.
Target: i386-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-languages=c,c++,objc,obj-c++,java,fortran,ada --enable-java-awt=gtk --disable-dssi --enable-plugin --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-cpu=generic --build=i386-redhat-linux
Thread model: posix
gcc version 4.3.2 20081105 (Red Hat 4.3.2-7) (GCC)
COLLECT_GCC_OPTIONS='-v' '-E' '-mtune=generic'
/usr/libexec/gcc/i386-redhat-linux/4.3.2/cc1 -E -quiet -v main.c -mtune=generic
ignoring nonexistent directory "/usr/lib/gcc/i386-redhat-linux/4.3.2/include-fixed"
ignoring nonexistent directory "/usr/lib/gcc/i386-redhat-linux/4.3.2/../../../../i386-redhat-linux/include"
#include "..." search starts here:
#include <...> search starts here:
/usr/local/include
/usr/lib/gcc/i386-redhat-linux/4.3.2/include
/usr/include
End of search list.
COMPILER_PATH=/usr/libexec/gcc/i386-redhat-linux/4.3.2/:/usr/libexec/gcc/i386-redhat-linux/4.3.2/:/usr/libexec/gcc/i386-redhat-linux/:/usr/lib/gcc/i386-redhat-linux/4.3.2/:/usr/lib/gcc/i386-redhat-linux/:/usr/libexec/gcc/i386-redhat-linux/4.3.2/:/usr/libexec/gcc/i386-redhat-linux/:/usr/lib/gcc/i386-redhat-linux/4.3.2/:/usr/lib/gcc/i386-redhat-linux/
LIBRARY_PATH=/usr/lib/gcc/i386-redhat-linux/4.3.2/:/usr/lib/gcc/i386-redhat-linux/4.3.2/:/usr/lib/gcc/i386-redhat-linux/4.3.2/../../../:/lib/:/usr/lib/
COLLECT_GCC_OPTIONS='-v' '-E' '-mtune=generic'
[zlg@localhost gdbtest]$ gcc main.c -S -v /*直接生成汇编文件*/
Using built-in specs.
Target: i386-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-languages=c,c++,objc,obj-c++,java,fortran,ada --enable-java-awt=gtk --disable-dssi --enable-plugin --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-cpu=generic --build=i386-redhat-linux
Thread model: posix
gcc version 4.3.2 20081105 (Red Hat 4.3.2-7) (GCC)
COLLECT_GCC_OPTIONS='-S' '-v' '-mtune=generic'
/usr/libexec/gcc/i386-redhat-linux/4.3.2/cc1 -quiet -v main.c -quiet -dumpbase main.c -mtune=generic -auxbase main -version -o main.s
ignoring nonexistent directory "/usr/lib/gcc/i386-redhat-linux/4.3.2/include-fixed"
ignoring nonexistent directory "/usr/lib/gcc/i386-redhat-linux/4.3.2/../../../../i386-redhat-linux/include"
#include "..." search starts here:
#include <...> search starts here:
/usr/local/include
/usr/lib/gcc/i386-redhat-linux/4.3.2/include
/usr/include
End of search list.
GNU C (GCC) version 4.3.2 20081105 (Red Hat 4.3.2-7) (i386-redhat-linux)
compiled by GNU C version 4.3.2 20081105 (Red Hat 4.3.2-7), GMP version 4.2.2, MPFR version 2.3.2.
GGC heuristics: --param ggc-min-expand=59 --param ggc-min-heapsize=55617
Compiler executable checksum: 3bee52601079f736b7b63b762646f4ba
COMPILER_PATH=/usr/libexec/gcc/i386-redhat-linux/4.3.2/:/usr/libexec/gcc/i386-redhat-linux/4.3.2/:/usr/libexec/gcc/i386-redhat-linux/:/usr/lib/gcc/i386-redhat-linux/4.3.2/:/usr/lib/gcc/i386-redhat-linux/:/usr/libexec/gcc/i386-redhat-linux/4.3.2/:/usr/libexec/gcc/i386-redhat-linux/:/usr/lib/gcc/i386-redhat-linux/4.3.2/:/usr/lib/gcc/i386-redhat-linux/
LIBRARY_PATH=/usr/lib/gcc/i386-redhat-linux/4.3.2/:/usr/lib/gcc/i386-redhat-linux/4.3.2/:/usr/lib/gcc/i386-redhat-linux/4.3.2/../../../:/lib/:/usr/lib/
COLLECT_GCC_OPTIONS='-S' '-v' '-mtune=generic'