【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】-库文件》,此处不再赘述。

 

posted @ 2021-01-26 15:16  Albert-陌尘  阅读(220)  评论(0编辑  收藏  举报