【Linux】-GCC
1. 拆解编译过程
1.1 Windows 编译
在 Windows 上开发,通常会使用 Visual Studio 等 IDE。代码写完后直接点编译按钮,就能生成可执行文件。这种方式虽然方便,但隐藏了编译过程中更为细分的步骤。
1.2 Linux 编译
在 Linux 上开发,通常使用 GCC 编译。我们可以更为细致地观察编译过程。hello.c 代码如下:
1 /* 2 * hello.c 3 */ 4 #include <stdio.h> 5 6 #define MAX 20 7 #define MIN 10 8 9 #define _DEBUG 10 #define SetBit(x) (1<<x) 11 12 int main(int argc, char *argv[]) 13 { 14 printf("Hello world!\n"); 15 printf("MAX = %d, MIN = %d, MAX+MIN = %d\n", MAX, MIN, MAX+MIN); 16 17 #ifdef _DEBUG 18 printf("SetBit(5) = %d, SetBit(6) = %d\n", SetBit(5), SetBit(6)); 19 printf("SetBit(SetBit(2)) = %d\n", SetBit(SetBit(2))); 20 #endif 21 22 return 0; 23 }
编译过程由以下几个步骤组成:
编译步骤拆分示例
2. GCC 简介
2.1 什么是 GCC
GCC(GNU Compiler Collection,GNU 编译器套件)是 Linux 下主要的编译工具。GCC 可以编译 C/C++、Java 等多个语言的程序。
2.2 使用 GCC 的原因
功能强大且稳定,开源免费。
3. GCC 常用选项
GCC 命令格式通常如下:
1 gcc [选项] 文件名
3.1 gcc --help
查看帮助信息,其中列出了各选项的具体用途。
1 albert@AlbertUltra:~$ gcc --help 2 Usage: gcc [options] file... 3 Options: 4 -pass-exit-codes Exit with highest error code from a phase 5 --help Display this information 6 --target-help Display target specific command line options 7 --help={common|optimizers|params|target|warnings|[^]{joined|separate|undocumented}}[,...] 8 Display specific types of command line options 9 (Use '-v --help' to display command line options of sub-processes) 10 --version Display compiler version information 11 -dumpspecs Display all of the built in spec strings 12 -dumpversion Display the version of the compiler 13 -dumpmachine Display the compiler's target processor 14 -print-search-dirs Display the directories in the compiler's search path 15 -print-libgcc-file-name Display the name of the compiler's companion library 16 -print-file-name=<lib> Display the full path to library <lib> 17 -print-prog-name=<prog> Display the full path to compiler component <prog> 18 -print-multiarch Display the target's normalized GNU triplet, used as 19 a component in the library path 20 -print-multi-directory Display the root directory for versions of libgcc 21 -print-multi-lib Display the mapping between command line options and 22 multiple library search directories 23 -print-multi-os-directory Display the relative path to OS libraries 24 -print-sysroot Display the target libraries directory 25 -print-sysroot-headers-suffix Display the sysroot suffix used to find headers 26 -Wa,<options> Pass comma-separated <options> on to the assembler 27 -Wp,<options> Pass comma-separated <options> on to the preprocessor 28 -Wl,<options> Pass comma-separated <options> on to the linker 29 -Xassembler <arg> Pass <arg> on to the assembler 30 -Xpreprocessor <arg> Pass <arg> on to the preprocessor 31 -Xlinker <arg> Pass <arg> on to the linker 32 -save-temps Do not delete intermediate files 33 -save-temps=<arg> Do not delete intermediate files 34 -no-canonical-prefixes Do not canonicalize paths when building relative 35 prefixes to other gcc components 36 -pipe Use pipes rather than intermediate files 37 -time Time the execution of each subprocess 38 -specs=<file> Override built-in specs with the contents of <file> 39 -std=<standard> Assume that the input sources are for <standard> 40 --sysroot=<directory> Use <directory> as the root directory for headers 41 and libraries 42 -B <directory> Add <directory> to the compiler's search paths 43 -v Display the programs invoked by the compiler 44 -### Like -v but options quoted and commands not executed 45 -E Preprocess only; do not compile, assemble or link 46 -S Compile only; do not assemble or link 47 -c Compile and assemble, but do not link 48 -o <file> Place the output into <file> 49 -pie Create a position independent executable 50 -shared Create a shared library 51 -x <language> Specify the language of the following input files 52 Permissible languages include: c c++ assembler none 53 'none' means revert to the default behavior of 54 guessing the language based on the file's extension 55 56 Options starting with -g, -f, -m, -O, -W, or --param are automatically 57 passed on to the various sub-processes invoked by gcc. In order to pass 58 other options on to these processes the -W<letter> options must be used. 59 60 For bug reporting instructions, please see: 61 <file:///usr/share/doc/gcc-4.8/README.Bugs>. 62 albert@AlbertUltra:~$
从以上 help 信息可以看到,gcc 有很多功能选项。但在实际使用中,常用的选项并不多,重点掌握这些选项即可。
3.2 -v 选项
3.2.1 显示版本信息
单独输入 gcc -v,屏幕上会显示 gcc 的版本信息。我们也可以用此命令来检测 gcc 是否工作正常。
1 albert@AlbertUltra:~$ gcc -v 2 Using built-in specs. 3 COLLECT_GCC=gcc 4 COLLECT_LTO_WRAPPER=/usr/lib/gcc/x86_64-linux-gnu/4.8/lto-wrapper 5 Target: x86_64-linux-gnu 6 Configured with: ../src/configure -v --with-pkgversion='Ubuntu 4.8.4-2ubuntu1~14.04.4'
--with-bugurl=file:///usr/share/doc/gcc-4.8/README.Bugs --enable-languages=c,c++,java,go,d,fortran,objc,obj-c++ --prefix=/usr --program-suffix=-4.8
--enable-shared --enable-linker-build-id --libexecdir=/usr/lib --without-included-gettext --enable-threads=posix --with-gxx-include-dir=/usr/include/c++/4.8
--libdir=/usr/lib --enable-nls --with-sysroot=/ --enable-clocale=gnu --enable-libstdcxx-debug --enable-libstdcxx-time=yes --enable-gnu-unique-object
--disable-libmudflap --enable-plugin --with-system-zlib --disable-browser-plugin --enable-java-awt=gtk --enable-gtk-cairo
--with-java-home=/usr/lib/jvm/java-1.5.0-gcj-4.8-amd64/jre --enable-java-home --with-jvm-root-dir=/usr/lib/jvm/java-1.5.0-gcj-4.8-amd64
--with-jvm-jar-dir=/usr/lib/jvm-exports/java-1.5.0-gcj-4.8-amd64 --with-arch-directory=amd64 --with-ecj-jar=/usr/share/java/eclipse-ecj.jar
--enable-objc-gc --enable-multiarch --disable-werror --with-arch-32=i686 --with-abi=m64 --with-multilib-list=m32,m64,mx32 --with-tune=generic
--enable-checking=release --build=x86_64-linux-gnu --host=x86_64-linux-gnu --target=x86_64-linux-gnu 7 Thread model: posix 8 gcc version 4.8.4 (Ubuntu 4.8.4-2ubuntu1~14.04.4) 9 albert@AlbertUltra:~$
3.2.2 显示 gcc 执行时的详细过程
-v 选项和其他选项配合使用,将显示 gcc 命令执行的详细过程。
3.3 -o 选项
-o 选项在 help 信息中描述如下:-o <file>:Place the output into <file>。也就是指定生成文件的名称。-o 选项可以和其他选项搭配使用。
3.4 -E 选项
-E 选项在 help 信息中描述如下:Preprocess only; do not compile, assamble or link。简而言之,-E 的功能就是预编译,譬如展开头文件,替换宏定义,根据条件编译选择编译哪部分代码等。
我们使用以下命令生成 hello.i,这是 hello.c 经过预处理的结果。
1 gcc -E -o hello.i hello.c
对比 hello.c 和 hello.i,可以更形象地了解预编译的效果。限于篇幅,不将具体差异贴在此处。
3.5 -S 选项
-S 选项在 help 信息中描述如下:compile only; do not assemble or link。这个选项用于编译阶段,将预处理生成的 .i 编译成 .s。
1 gcc -S -o hello.s hello.i
hello.s 中的内容如下:
1 albert@AlbertUltra:~/coding/gcc$ cat hello.s 2 .file "hello.c" 3 .section .rodata 4 .LC0: 5 .string "Hello world!" 6 .align 8 7 .LC1: 8 .string "MAX = %d, MIN = %d, MAX+MIN = %d\n" 9 .align 8 10 .LC2: 11 .string "SetBit(5) = %d, SetBit(6) = %d\n" 12 .LC3: 13 .string "SetBit(SetBit(2)) = %d\n" 14 .text 15 .globl main 16 .type main, @function 17 main: 18 .LFB0: 19 .cfi_startproc 20 pushq %rbp 21 .cfi_def_cfa_offset 16 22 .cfi_offset 6, -16 23 movq %rsp, %rbp 24 .cfi_def_cfa_register 6 25 subq $16, %rsp 26 movl %edi, -4(%rbp) 27 movq %rsi, -16(%rbp) 28 movl $.LC0, %edi 29 call puts 30 movl $30, %ecx 31 movl $10, %edx 32 movl $20, %esi 33 movl $.LC1, %edi 34 movl $0, %eax 35 call printf 36 movl $64, %edx 37 movl $32, %esi 38 movl $.LC2, %edi 39 movl $0, %eax 40 call printf 41 movl $16, %esi 42 movl $.LC3, %edi 43 movl $0, %eax 44 call printf 45 movl $0, %eax 46 leave 47 .cfi_def_cfa 7, 8 48 ret 49 .cfi_endproc 50 .LFE0: 51 .size main, .-main 52 .ident "GCC: (Ubuntu 4.8.4-2ubuntu1~14.04.4) 4.8.4" 53 .section .note.GNU-stack,"",@progbits 54 albert@AlbertUltra:~/coding/gcc$
可以看到,经过编译处理,已经将源文件中的 C 语言代码转换成汇编代码。
3.6 -c 选项
-c 选项在 help 信息中描述如下:Compile and assemble, but do not link。也就是说,这个步骤是编译和汇编的,但不做链接。这一步生成 .o 文件,即 object file,也称 OBJ 文件。
1 gcc -c -o hello.o hello.s
通过 file 命令查看 hello.o,可以看到,它是一个 ELF(Exacutable and Linkable File Format,可执行与可链接文件格式)文件。也就是说,这一步生成的文件其实已经是机器码。但我们不能直接运行 .o 文件,因为 .o 不是可执行文件,而是目标文件。还要通过链接器把多个目标文件以及函数库链接起来,才能生成可执行文件。
3.7 链接
链接就是将汇编生成的OBJ文件、系统库的OBJ文件、库文件链接起来,最终生成可以在特定平台运行的可执行程序。
通过以下命令链接,查看具体信息:
1 albert@AlbertUltra:~/coding/gcc$ gcc -v -o hello hello.o 2 Using built-in specs. 3 COLLECT_GCC=gcc 4 COLLECT_LTO_WRAPPER=/usr/lib/gcc/x86_64-linux-gnu/4.8/lto-wrapper 5 Target: x86_64-linux-gnu 6 Configured with: ../src/configure -v --with-pkgversion='Ubuntu 4.8.4-2ubuntu1~14.04.4' --with-bugurl=file:///usr/share/doc/gcc-4.8/README.Bugs
--enable-languages=c,c++,java,go,d,fortran,objc,obj-c++ --prefix=/usr --program-suffix=-4.8 --enable-shared --enable-linker-build-id
--libexecdir=/usr/lib --without-included-gettext --enable-threads=posix --with-gxx-include-dir=/usr/include/c++/4.8 --libdir=/usr/lib
--enable-nls --with-sysroot=/ --enable-clocale=gnu --enable-libstdcxx-debug --enable-libstdcxx-time=yes --enable-gnu-unique-object
--disable-libmudflap --enable-plugin --with-system-zlib --disable-browser-plugin --enable-java-awt=gtk --enable-gtk-cairo
--with-java-home=/usr/lib/jvm/java-1.5.0-gcj-4.8-amd64/jre --enable-java-home --with-jvm-root-dir=/usr/lib/jvm/java-1.5.0-gcj-4.8-amd64
--with-jvm-jar-dir=/usr/lib/jvm-exports/java-1.5.0-gcj-4.8-amd64 --with-arch-directory=amd64 --with-ecj-jar=/usr/share/java/eclipse-ecj.jar
--enable-objc-gc --enable-multiarch --disable-werror --with-arch-32=i686 --with-abi=m64 --with-multilib-list=m32,m64,mx32 --with-tune=generic
--enable-checking=release --build=x86_64-linux-gnu --host=x86_64-linux-gnu --target=x86_64-linux-gnu 7 Thread model: posix 8 gcc version 4.8.4 (Ubuntu 4.8.4-2ubuntu1~14.04.4) 9 COMPILER_PATH=/usr/lib/gcc/x86_64-linux-gnu/4.8/:/usr/lib/gcc/x86_64-linux-gnu/4.8/:/usr/lib/gcc/x86_64-linux-gnu/
:/usr/lib/gcc/x86_64-linux-gnu/4.8/:/usr/lib/gcc/x86_64-linux-gnu/ 10 LIBRARY_PATH=/usr/lib/gcc/x86_64-linux-gnu/4.8/:/usr/lib/gcc/x86_64-linux-gnu/4.8/../../../x86_64-linux-gnu/:
/usr/lib/gcc/x86_64-linux-gnu/4.8/../../../../lib/:/lib/x86_64-linux-gnu/
:/lib/../lib/:/usr/lib/x86_64-linux-gnu/:/usr/lib/../lib/:/usr/lib/gcc/x86_64-linux-gnu/4.8/../../../:/lib/:/usr/lib/ 11 COLLECT_GCC_OPTIONS='-v' '-o' 'hello' '-mtune=generic' '-march=x86-64' 12 /usr/lib/gcc/x86_64-linux-gnu/4.8/collect2 --sysroot=/ --build-id --eh-frame-hdr
-m elf_x86_64 --hash-style=gnu --as-needed -dynamic-linker /lib64/ld-linux-x86-64.so.2
-z relro -o hello /usr/lib/gcc/x86_64-linux-gnu/4.8/../../../x86_64-linux-gnu/crt1.o
/usr/lib/gcc/x86_64-linux-gnu/4.8/../../../x86_64-linux-gnu/crti.o
/usr/lib/gcc/x86_64-linux-gnu/4.8/crtbegin.o -L/usr/lib/gcc/x86_64-linux-gnu/4.8
-L/usr/lib/gcc/x86_64-linux-gnu/4.8/../../../x86_64-linux-gnu -L/usr/lib/gcc/x86_64-linux-gnu/4.8/../../../../lib
-L/lib/x86_64-linux-gnu -L/lib/../lib -L/usr/lib/x86_64-linux-gnu -L/usr/lib/../lib
-L/usr/lib/gcc/x86_64-linux-gnu/4.8/../../.. hello.o -lgcc --as-needed -lgcc_s --no-as-needed
-lc -lgcc --as-needed -lgcc_s --no-as-needed /usr/lib/gcc/x86_64-linux-gnu/4.8/crtend.o
/usr/lib/gcc/x86_64-linux-gnu/4.8/../../../x86_64-linux-gnu/crtn.o 13 albert@AlbertUltra:~/coding/gcc$
crt1.o、crti.o、crtbegin.o、crtend.o、crtn.o 是 gcc 加入的系统标准启动文件,对于一般应用程序,这些启动文件是必需的。
-lc:链接 libc 库文件,其中 libc 库文件中就实现了 printf 等函数。
3.8 -nostdlib 选项
-nostdlib 不链接系统标准启动文件和标准库文件,只把指定的文件传递给链接器。
-nostdlib 选项常用于裸机、bootloader、Linux 内核等程序,因为它们不需要启动文件、标准库文件。对于普通应用程序,如果添加 -nostdlib 选项,会提示没有链接系统标准启动文件和标准库文件,导致链接失败。
3.9 文件类型对于编译的影响
在上述小节中,我们专注于解释各个选项的作用。现在我们要返回来,看看文件类型对于编译的影响。
在编译过程中,除非使用了 -E、-S、-c 选项或者编译出错,否则最后的步骤都是链接。输入文件的后缀名和选项共同决定 gcc 执行哪些操作。
这样说可能还不是很直观。我们举个例子来看看。
1 # 示例一 2 gcc -o hello hello.i 3 4 # 示例二 5 gcc -o hello hello.s 6 7 # 示例三 8 gcc -o hello hello.o
以上三种方式,都会生成可执行文件 hello,并且能够成功运行,但给出的文件类型不同。
示例一中,给出的是 .i 文件,因此 gcc 对其进行编译、汇编、链接,生成可执行文件。
示例二中,给出的是 .s 文件,因此 gcc 对其进行汇编、链接,生成可执行文件。
示例三中,给出的是 .o 文件,因此 gcc 对其进行链接,生成可执行文件。
简而言之,对于不同文件类型,能进行的操作是不同的。这也会对编译产生影响。
3.10 库文件
关于库文件的使用,前面已经写过一篇《【Linux】-库文件》,此处不再赘述。