编译链接
编译链接其实包含一系列过程。
还是以上面的例子为例来讲解编译链接,采用gcc编译器:
#include <stdio.h> void main() { printf("hello, c!\n"); }
可以将编译链接分开进行,通常我们可以将这两个过程一起完成。上面的那个例子:
直接编译链接生成可执行程序
>gcc maintest.c -o maintest
>.\maintest
hello, c!
我们可以查看这一次编译链接过程的细节:
>gcc maintest.c -o maintest -v
Using built-in specs.
COLLECT_GCC=gcc
COLLECT_LTO_WRAPPER=/usr/lib/gcc/i686-pc-cygwin/4.5.3/lto-wrapper.exe
Target: i686-pc-cygwin
Configured with: /gnu/gcc/releases/respins/4.5.3-3/gcc4-4.5.3-3/src/gcc-4.5.3/configure --srcdir=/gnu/gcc/releases/respins/4.5.3-3/gcc4-4.5.3-3/src/gcc-4.5.3 --prefix=/usr --exec-prefix=/usr --bindir=/usr/bin --sbindir=/usr/sbin --libexecdir=/usr/lib --datadir=/usr/share --localstatedir=/var --sysconfdir=/etc --datarootdir=/usr/share --docdir=/usr/share/doc/gcc4 -C --datadir=/usr/share --infodir=/usr/share/info --mandir=/usr/share/man -v --with-gmp=/usr --with-mpfr=/usr --enable-bootstrap --enable-version-specific-runtime-libs --libexecdir=/usr/lib --enable-static --enable-shared --enable-shared-libgcc --disable-__cxa_atexit --with-gnu-ld --with-gnu-as --with-dwarf2 --disable-sjlj-exceptions --enable-languages=ada,c,c++,fortran,java,lto,objc,obj-c++ --enable-graphite --enable-lto --enable-java-awt=gtk --disable-symvers --enable-libjava --program-suffix=-4 --enable-libgomp --enable-libssp --enable-libada --enable-threads=posix --with-arch=i686 --with-tune=generic --enable-libgcj-sublibs CC=gcc-4 CXX=g++-4 CC_FOR_TARGET=gcc-4 CXX_FOR_TARGET=g++-4 GNATMAKE_FOR_TARGET=gnatmake GNATBIND_FOR_TARGET=gnatbind --with-ecj-jar=/usr/share/java/ecj.jar
Thread model: posix
gcc version 4.5.3 (GCC)
COLLECT_GCC_OPTIONS='-o' 'maintest.exe' '-v' '-mtune=generic' '-march=i686'
/usr/lib/gcc/i686-pc-cygwin/4.5.3/cc1.exe -quiet -v -D__CYGWIN32__ -D__CYGWIN__ -Dunix -D__unix__ -D__unix -idirafter /usr/lib/gcc/i686-pc-cygwin/4.5.3/../../../../include/w32api -idirafter /usr/lib/gcc/i686-pc-cygwin/4.5.3/../../../../i686-pc-cygwin/lib/../../include/w32api maintest.c -quiet -dumpbase maintest.c -mtune=generic -march=i686 -auxbase maintest -version -o /cygdrive/c/DOCUME~1/ADMINI~1/LOCALS~1/Temp/cceNNRyv.s
GNU C (GCC) version 4.5.3 (i686-pc-cygwin)
compiled by GNU C version 4.5.3, GMP version 4.3.2, MPFR version 3.0.1-p4, MPC version 0.8
GGC heuristics: --param ggc-min-expand=100 --param ggc-min-heapsize=131072
ignoring nonexistent directory "/usr/lib/gcc/i686-pc-cygwin/4.5.3/../../../../i686-pc-cygwin/include"
ignoring duplicate directory "/usr/lib/gcc/i686-pc-cygwin/4.5.3/../../../../i686-pc-cygwin/lib/../../include/w32api"
#include "..." search starts here:
#include <...> search starts here:
/usr/local/include
/usr/lib/gcc/i686-pc-cygwin/4.5.3/include
/usr/lib/gcc/i686-pc-cygwin/4.5.3/include-fixed
/usr/include
/usr/lib/gcc/i686-pc-cygwin/4.5.3/../../../../include/w32api
End of search list.
GNU C (GCC) version 4.5.3 (i686-pc-cygwin)
compiled by GNU C version 4.5.3, GMP version 4.3.2, MPFR version 3.0.1-p4, MPC version 0.8
GGC heuristics: --param ggc-min-expand=100 --param ggc-min-heapsize=131072
Compiler executable checksum: 89d6774c1d510265da7d48b735ce61fb
COLLECT_GCC_OPTIONS='-o' 'maintest.exe' '-v' '-mtune=generic' '-march=i686'
/usr/lib/gcc/i686-pc-cygwin/4.5.3/../../../../i686-pc-cygwin/bin/as.exe -v -o /cygdrive/c/DOCUME~1/ADMINI~1/LOCALS~1/Temp/ccrMX9kq.o /cygdrive/c/DOCUME~1/ADMINI~1/LOCALS~1/Temp/cceNNRyv.s
GNU assembler version 2.23.52 (i686-cygwin) using BFD version (GNU Binutils) 2.23.52.20130309
COMPILER_PATH=/usr/lib/gcc/i686-pc-cygwin/4.5.3/:/usr/lib/gcc/i686-pc-cygwin/4.5.3/:/usr/lib/gcc/i686-pc-cygwin/:/usr/lib/gcc/i686-pc-cygwin/4.5.3/:/usr/lib/gcc/i686-pc-cygwin/:/usr/lib/gcc/i686-pc-cygwin/4.5.3/../../../../i686-pc-cygwin/bin/
LIBRARY_PATH=/usr/lib/gcc/i686-pc-cygwin/4.5.3/:/usr/lib/gcc/i686-pc-cygwin/4.5.3/../../../:/lib/:/usr/lib/
COLLECT_GCC_OPTIONS='-o' 'maintest.exe' '-v' '-mtune=generic' '-march=i686'
/usr/lib/gcc/i686-pc-cygwin/4.5.3/collect2.exe --wrap _Znwj --wrap _Znaj --wrap _ZdlPv --wrap _ZdaPv --wrap _ZnwjRKSt9nothrow_t --wrap _ZnajRKSt9nothrow_t --wrap _ZdlPvRKSt9nothrow_t --wrap _ZdaPvRKSt9nothrow_t -Bdynamic --dll-search-prefix=cyg --large-address-aware --tsaware -o maintest.exe /usr/lib/gcc/i686-pc-cygwin/4.5.3/../../../crt0.o /usr/lib/gcc/i686-pc-cygwin/4.5.3/crtbegin.o -L/usr/lib/gcc/i686-pc-cygwin/4.5.3 -L/usr/lib/gcc/i686-pc-cygwin/4.5.3/../../.. /cygdrive/c/DOCUME~1/ADMINI~1/LOCALS~1/Temp/ccrMX9kq.o -lgcc -lgcc_eh -lcygwin -luser32 -lkernel32 -ladvapi32 -lshell32 -lgcc -lgcc_eh /usr/lib/gcc/i686-pc-cygwin/4.5.3/crtend.o
可以看到这个编译链接过程还挺复杂,分成3个步骤,其中:
/usr/lib/gcc/i686-pc-cygwin/4.5.3/cc1.exe -quiet -v -D__CYGWIN32__ -D__CYGWIN__ -Dunix -D__unix__ -D__unix -idirafter /usr/lib/gcc/i686-pc-cygwin/4.5.3/../../../../include/w32api -idirafter /usr/lib/gcc/i686-pc-cygwin/4.5.3/../../../../i686-pc-cygwin/lib/../../include/w32api maintest.c -quiet -dumpbase maintest.c -mtune=generic -march=i686 -auxbase maintest -version -o /cygdrive/c/DOCUME~1/ADMINI~1/LOCALS~1/Temp/cceNNRyv.s
这段是对maintest.c进行汇编编译成汇编程序cceNNRyv.s。这里用到了一个cc1.exe工具,这个工具我们一般用不到,它是一个将C程序汇编编译成汇编程序的工具。
它的效果其实就跟下面的一样:
>gcc -S maintest.c -o maintest.s
生成的汇编程序如下:
.file "maintest.c" .def ___main; .scl 2; .type 32; .endef .section .rdata,"dr" LC0: .ascii "hello, c!\0" .text .globl _main .def _main; .scl 2; .type 32; .endef _main: pushl %ebp movl %esp, %ebp andl $-16, %esp subl $16, %esp call ___main movl $LC0, (%esp) call _puts leave ret .def _puts; .scl 2; .type 32; .endef
/usr/lib/gcc/i686-pc-cygwin/4.5.3/../../../../i686-pc-cygwin/bin/as.exe -v -o /cygdrive/c/DOCUME~1/ADMINI~1/LOCALS~1/Temp/ccrMX9kq.o /cygdrive/c/DOCUME~1/ADMINI~1/LOCALS~1/Temp/cceNNRyv.s
这段是将上面的汇编程序cceNNRyv.s编译成对象文件ccrMX9kq.o。通过as工具,as这个工具如果我们用过as汇编的话就对它很熟,它是一个汇编编译器。
我们上面的编译过程实际上还包括这两个过程:
1、将c程序汇编编译成汇编程序
2、将汇编程序编译成对象文件
也就是说:
gcc -c maintest.c -o maintest.o
实际上包括上面这两个步骤。
/usr/lib/gcc/i686-pc-cygwin/4.5.3/collect2.exe --wrap _Znwj --wrap _Znaj --wrap _ZdlPv --wrap _ZdaPv --wrap _ZnwjRKSt9nothrow_t --wrap _ZnajRKSt9nothrow_t --wrap _ZdlPvRKSt9nothrow_t --wrap _ZdaPvRKSt9nothrow_t -Bdynamic --dll-search-prefix=cyg --large-address-aware --tsaware -o maintest.exe /usr/lib/gcc/i686-pc-cygwin/4.5.3/../../../crt0.o /usr/lib/gcc/i686-pc-cygwin/4.5.3/crtbegin.o -L/usr/lib/gcc/i686-pc-cygwin/4.5.3 -L/usr/lib/gcc/i686-pc-cygwin/4.5.3/../../.. /cygdrive/c/DOCUME~1/ADMINI~1/LOCALS~1/Temp/ccrMX9kq.o -lgcc -lgcc_eh -lcygwin -luser32 -lkernel32 -ladvapi32 -lshell32 -lgcc -lgcc_eh /usr/lib/gcc/i686-pc-cygwin/4.5.3/crtend.o
这段是将上面的ccrMX9kq.o链接成可执行程序maintest.exe。这里用到了一个collect2.exe工具,这个是一个链接工具。
这个工具我们一般也用不到,实际上我们单独链接的话,也不会用到这个工具,我们会通过ld工具或者直接通过gcc进行单独链接。但这个工具其实就是ld工具的一个封装,它用的气势就是ld工具来进行链接。当然,通过gcc进行单独链接最后也是通过ld工具来进行链接。
所以,如果已经编译了的话, 我们可以通过这个工具单独进行链接:
>gcc -c maintest.c -o maintest.o
>D:\sbin\lib\gcc\i686-pc-cygwin\4.5.3\collect2.exe --wrap _Znwj --wrap _Znaj --wrap _ZdlPv --wrap _ZdaPv --wrap _ZnwjRKSt9nothrow_t --wrap _ZnajRKSt9nothrow_t --wrap _ZdlPvRKSt9nothrow_t --wrap _ZdaPvRKSt9nothrow_t -Bdynamic --dll-search-prefix=cyg --large-address-aware --tsaware -o maintest.exe /usr/lib/gcc/i686-pc-cygwin/4.5.3/../../../crt0.o /usr/lib/gcc/i686-pc-cygwin/4.5.3/crtbegin.o -L/usr/lib/gcc/i686-pc-cygwin/4.5.3 -L/usr/lib/gcc/i686-pc-cygwin/4.5.3/../../.. maintest.o -lgcc -lgcc_eh -lcygwin -luser32 -lkernel32 -ladvapi32 -lshell32 -lgcc -lgcc_eh /usr/lib/gcc/i686-pc-cygwin/4.5.3/crtend.o
>.\maintest
hello, c!
上面通过collect2.exe工具链接的时候指定了很多多余的选项以及链接库,很多都没有用到,去掉没有用到的,其实很简单:
>gcc -c maintest.c -o maintest.o
>D:\sbin\lib\gcc\i686-pc-cygwin\4.5.3\collect2.exe -o maintest.exe /usr/lib/gcc/i686-pc-cygwin/4.5.3/../../../crt0.o maintest.o -lcygwin -lkernel32
可以通过以下命令dump查看cygwin和crt0.o的内部实现:
$ objdump -d libcygwin.a
$ objdump -d /usr/lib/gcc/i686-pc-cygwin/4.5.3/../../../crt0.o
>.\maintest
hello, c!
通常如果工程简单的话,我们可以一次完成整个过程,如果复杂的话,我们会分两次或多次完成,简单来讲,它分为编译和链接两个过程。
如果将编译和链接分开的话,
编译
>gcc -c maintest.c -o maintest.o
链接
>gcc maintest.o -o maintest
>.\maintest
hello, c!
通过ld工具进行链接
>ld maintest.o -o maintest
这样会报错:
maintest.o:maintest.c:(.text+0xa): undefined reference to `__main'
maintest.o:maintest.c:(.text+0x16): undefined reference to `puts'
缺失的__main和puts实际上在cygwin链接库中,需要加上-lcygwin。同时mainCRTStartup入口函数在/usr/lib/gcc/i686-pc-cygwin/4.5.3/../../../crt0.o中,链接的时候需要将这个对象文件一起链入进去。另外还需要-lkernel32,链接的时候依赖这个库。
puts函数的实现:
Disassembly of section .text:
00000000 <_puts>:
0: ff 25 00 00 00 00 jmp *0x0
6: 90 nop
7: 90 nop
t-d001504.o: 文件格式 pe-i386
所以,应该这样:
>gcc -c maintest.c -o maintest.o
>ld -o maintest.exe /usr/lib/gcc/i686-pc-cygwin/4.5.3/../../../crt0.o maintest.o -lcygwin -lkernel32
这里其实就是直接将上面通过collect2.exe工具链接的时候,直接将collect2.exe替换为ld工具进行链接。
>.\maintest
hello, c!