GCC编译器的使用

主要参考野火电子:http://doc.embedfire.com/linux/imx6/base/zh/latest/index.html

1 GCC 编译工具链

1.1 简介

GCC 编译工具链(toolchain)是指以 GCC 编译器为核心的一整套工具,用于把源代码转化成可执行应用程序。它主要包含以下三部分内容:

  • gcc-core :即GCC编译器,用于完成预处理和编译过程,例如把C代码转换成汇编代码。

  • Binutils :除GCC编译器外的一系列小工具包括了链接器 ld ,汇编器 as 、目标文件格式查看器 readelf 等。

  • glibc :包含了主要的 C 语言标准函数库,C 语言中常常使用的打印函数 printfmalloc 函数就在 glibc 库中。

在很多场合下会直接用 GCC 编译器 来指代整套 GCC编译工具链。

1.2 GCC 编译器

GCCGNU Compiler Collection)是由 GNU 开发的编程语言编译器。 GCC 最初代表 “GNU C Compiler” ,当时只支持C语言。 后来又扩展能够支持更多编程语言,包括 C++FortranJava 等。 因此,GCC 也被重新定义为 “GNU Compiler Collection” ,成为历史上最优秀的编译器, 其执行效率与一般的编译器相比平均效率要高 20%~30%。GCC的官网地址

Ubuntu 系统下系统默认已经安装好 GCC 编译器,可以通过命令查看 Ubuntu 系统中 GCC 编译器的版本及安装路径:
GCC编译器的使用_第1张图片

1.3 Binutils 工具集

Binutilsbin utility),是 GNU 二进制工具集,通常跟 GCC 编译器一起打包安装到系统。Binutils官方说明

在进行程序开发的时候通常不会直接调用这些工具,而是在使用 GCC 编译指令的时候由 GCC 编译器间接调用。下面是其中一些常用的工具:

  • as:汇编器,把汇编语言代码转换为机器码(目标文件)。
  • ld:链接器,把编译生成的多个目标文件组织成最终的可执行程序文件。
  • readelf :可用于查看目标文件或可执行程序文件的信息。
  • nm : 可用于查看目标文件中出现的符号。
  • objcopy : 可用于目标文件格式转换,如 .bin 转换成 .elf.elf 转换成 .bin 等。
  • objdump :可用于查看目标文件的信息,最主要的作用是反汇编。
  • size :可用于查看目标文件不同部分的尺寸和总尺寸,例如代码段大小、数据段大小、使用的静态内存、总大小等。

系统默认的 Binutils 工具集位于 /usr/bin 目录下。

1.4 glibc

glibc 库是 GNU 组织为 GNU 系统以及 Linux 系统编写的 C 语言标准库,因为绝大部分 C 程序都依赖该函数库,该文件甚至会直接影响到系统的正常运行,例如常用的文件操作函数 readwriteopen ,打印函数 printf 、动态内存申请函数 malloc等。

Ubuntu 系统下,libc.so.6glibc 的库文件,可直接执行该库文件查看版本:
GCC编译器的使用_第2张图片

2 GCC 编译过程

一个 C/C++ 文件要经过预处理、编译、汇编和链接等 4 步才能变成可执行文件。在日常中通常使用“编译”统称这 4 个步骤。

2.1 基本语法

2.1.1 GCC 使用的命令语法

$ gcc [option] 文件名

常用选项:

  • -o :小写字母“o”,指定生成的可执行文件的名字,不指定的话生成的可执行文件名为a.out。
  • -E :只进行预处理,既不编译,也不汇编。
  • -S :只编译,不汇编。
  • -c :编译并汇编,但不进行链接。
  • -g :生成的可执行文件带调试信息,方便使用gdb进行调试。
  • -Ox :大写字母“O”加数字,设置程序的优化等级,如“-O0”“-O1” “-O2” “-O3”, 数字越大代码的优化等级越高,编译出来的程序一般会越小,但有可能会导致程序不正常运行。
  • I :指定头文件目录
  • L :指定链接时库文件目录
  • l :指定链接哪一个库文件

2.1.2 编译过程

GCC 编译工具链在编译一个 C 源文件时需要经过以下 4 步:

  1. 预处理:在预处理过程中,对源代码文件中的文件包含(include)、 预编译语句(如宏定义define等)进行展开,生成 .i 文件。 可理解为把头文件的代码、宏之类的内容转换成更纯粹的C代码,不过生成的文件以 .i 为后缀。
  2. 编译:把预处理后的 .i 文件通过编译成为汇编语言,生成 .s 文件,即把代码从C语言转换成汇编语言,这是 GCC 编译器完成的工作。
  3. 汇编:将汇编语言文件经过汇编,生成目标文件 .o 文件,每一个源文件都对应一个目标文件。即把汇编语言的代码转换成机器码,这是 as 汇编器完成的工作。
  4. 链接:最后将每个源文件对应的 .o 文件链接起来,就生成一个可执行程序文件,这是链接器 ld 完成的工作。

源码:src/hello.c,具体命令如下:

#直接编译成可执行文件
$ gcc hello.c -o hello

#以上命令等价于执行以下全部操作
#预处理,可理解为把头文件的代码汇总成C代码,把*.c转换得到*.i文件
$ gcc -E hello.c -o hello.i

#编译,可理解为把C代码转换为汇编代码,把*.i转换得到*.s文件
$ gcc -S hello.i -o hello.s

#汇编,可理解为把汇编代码转换为机器码,把*.s转换得到*.o,即目标文件
$ gcc -c hello.s -o hello.o

#链接,把不同文件之间的调用关系链接起来,把一个或多个*.o转换成最终的可执行文件
$ gcc hello.o -o hello

2.2 编译阶段分析

2.2.1 预处理阶段

  • 命令

    # 预处理,可理解为把头文件的代码汇总成C代码,把*.c转换得到*.i文件
    $ gcc -E hello.c -o hello.i
    
  • 结果

    $ vim hello.i
    # 1 "hello.c"
    # 1 ""
    # 1 ""
    # 31 ""
    # 1 "/usr/include/stdc-predef.h" 1 3 4
    # 32 "" 2
    # 1 "hello.c"
    # 1 "/usr/include/stdio.h" 1 3 4
    # 27 "/usr/include/stdio.h" 3 4
    # 1 "/usr/include/x86_64-linux-gnu/bits/libc-header-start.h" 1 3 4
    # 33 "/usr/include/x86_64-linux-gnu/bits/libc-header-start.h" 3 4
    # 1 "/usr/include/features.h" 1 3 4
    # 424 "/usr/include/features.h" 3 4
    # 1 "/usr/include/x86_64-linux-gnu/sys/cdefs.h" 1 3 4
    # 427 "/usr/include/x86_64-linux-gnu/sys/cdefs.h" 3 4
    # 1 "/usr/include/x86_64-linux-gnu/bits/wordsize.h" 1 3 4
    # 428 "/usr/include/x86_64-linux-gnu/sys/cdefs.h" 2 3 4
    # 1 "/usr/include/x86_64-linux-gnu/bits/long-double.h" 1 3 4
    # 429 "/usr/include/x86_64-linux-gnu/sys/cdefs.h" 2 3 4
    # 425 "/usr/include/features.h" 2 3 4
    # 448 "/usr/include/features.h" 3 4
    # 1 "/usr/include/x86_64-linux-gnu/gnu/stubs.h" 1 3 4
    # 10 "/usr/include/x86_64-linux-gnu/gnu/stubs.h" 3 4
    # 1 "/usr/include/x86_64-linux-gnu/gnu/stubs-64.h" 1 3 4
    # 11 "/usr/include/x86_64-linux-gnu/gnu/stubs.h" 2 3 4
    # 449 "/usr/include/features.h" 2 3 4
    # 34 "/usr/include/x86_64-linux-gnu/bits/libc-header-start.h" 2 3 4
    # 28 "/usr/include/stdio.h" 2 3 4
    
    # 此处省略了很多
    
    # 3 "hello.c"
    int main(int argc, char *argv[])
    {
     printf("hello world!\n");
     return 0;
    }
    

2.2.2 编译阶段

  • 命令

    # 编译,可理解为把C代码转换为汇编代码,把*.i转换得到*.s文件
    $ gcc -S hello.i -o hello.s
    
    # 也可以直接以C文件作为输入进行编译,与上面的命令是等价的
    $ gcc -S hello.c -o hello.s
    
  • 结果

    $ vim hello.s
    	.file	"hello.c"
    	.text
    	.section	.rodata
    .LC0:
    	.string	"hello world!"
    	.text
    	.globl	main
    	.type	main, @function
    main:
    .LFB0:
    	.cfi_startproc
    	pushq	%rbp
    	.cfi_def_cfa_offset 16
    	.cfi_offset 6, -16
    	movq	%rsp, %rbp
    	.cfi_def_cfa_register 6
    	subq	$16, %rsp
    	movl	%edi, -4(%rbp)
    	movq	%rsi, -16(%rbp)
    	leaq	.LC0(%rip), %rdi
    	call	puts@PLT
    	movl	$0, %eax
    	leave
    	.cfi_def_cfa 7, 8
    	ret
    	.cfi_endproc
    .LFE0:
    	.size	main, .-main
    	.ident	"GCC: (Ubuntu 7.5.0-3ubuntu1~18.04) 7.5.0"
    	.section	.note.GNU-stack,"",@progbits
    

2.2.3 汇编阶段

  • 命令

    # 汇编,可理解为把汇编代码转换为机器码,把*.s转换得到*.o,即目标文件,elf格式
    $ gcc -c hello.s -o hello.o
    
    # 也可以直接以C文件作为输入进行汇编,与上面的命令是等价的
    $ gcc -c hello.c -o hello.o
    
  • 结果

    $ readelf -a hello.o
    ELF Header:
      Magic:   7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
      Class:                             ELF64
      Data:                              2's complement, little endian
      Version:                           1 (current)
      OS/ABI:                            UNIX - System V
      ABI Version:                       0
      Type:                              REL (Relocatable file)
      Machine:                           Advanced Micro Devices X86-64
      Version:                           0x1
      Entry point address:               0x0
      Start of program headers:          0 (bytes into file)
      Start of section headers:          728 (bytes into file)
      Flags:                             0x0
      Size of this header:               64 (bytes)
      Size of program headers:           0 (bytes)
      Number of program headers:         0
      Size of section headers:           64 (bytes)
      Number of section headers:         13
      Section header string table index: 12
    # 省略了很多
    

2.2.4 链接阶段

  • 命令

    # 动态链接
    # 生成名为hello的可执行文件
    $ gcc hello.o -o hello
    
    # 也可以直接使用C文件一步生成,与上面的命令等价
    $ gcc hello.c -o hello
    
    # 静态链接
    # 使用-static参数,生成名为hello_static的可执行文件
    $ gcc hello.o -o hello_static -static
    
    # 也可以直接使用C文件一步生成,与上面的命令等价
    $ gcc hello.c -o hello_static -static
    
  • 结果

    $ gcc hello.o -o hello
    $ gcc hello.o -o hello_static -static
    $ ls -lh
    total 872K
    -rwxr-xr-x 1 root root 8.2K Jun 23 01:33 hello     		# 比较小,执行的时候会去找到动态库执行,所以要保证库文件目录有对应的库
    -rw-r--r-- 1 root root   95 Jun 23 01:21 hello.c
    -rw-r--r-- 1 root root  18K Jun 23 01:23 hello.i
    -rw-r--r-- 1 root root 1.6K Jun 23 01:26 hello.o
    -rw-r--r-- 1 root root  516 Jun 23 01:25 hello.s
    -rwxr-xr-x 1 root root 826K Jun 23 01:33 hello_static	# 把相关的函数打包进了可执行文件里,执行时不用担心库的问题
    
    # 使用ldd工具查看动态文件的库依赖
    $ ldd hello
        linux-vdso.so.1 (0x00007ffd95d72000)
        libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f86eeeaa000)
        /lib64/ld-linux-x86-64.so.2 (0x00007f86ef49d000)
    $ ldd hello_static
        not a dynamic executable  # 不是动态可执行文件
    

3 其他用法

3.1 怎么编译多个文件

3.1.1 源码

  • main.c

    #include 
    #include "sub.h"
    
    int main(int argc, char *argv[])
    {
       int i;
       printf("Main fun!\n");
       sub_fun();
       return 0;
    }
    
  • sub.c

    #include "sub.h"
    
    void sub_fun(void)
    {
       printf("Sub fun!\n");
    }
    
  • sub.h

    #ifndef _SUB_H
    #define _SUB_H
    
    void sub_fun(void);
    
    #endif /* _SUB_H */
    

3.1.2 编译

  • 一起编译,链接

    $ gcc -o test main.c sub.c
    
  • 分开编译,统一链接

    $ gcc -c -o main.o main.c
    $ gcc -c -o sub.o sub.c
    $ gcc -o test main.o sub.o
    

3.2 制作动态库

3.2.1 制作

  • 制作动态库

    # 可以使用多个 *.o 生成动态库
    $ gcc -c -o sub.o sub.c
    $ gcc -shared -o libsub.so sub.o
    
  • 生成可执行文件

    $ gcc -c -o main.o main.c
    $ gcc -o test main.o -L./ -lsub    # 使用 -L 指定库文件目录
    

3.2.2 运行

  • libusb.so 放到 PC 或板子上的 /lib 目录,然后就可以运行 test 程序。

  • 如果不想把 libusb.so 放到 /lib,也可以放在某个目录比如 /a ,然后如下执行

    $ export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/a
    $ ./test
    Main fun!
    Sub fun!
    

3.3 制作静态库

3.3.1 制作

  • 制作静态库

    # 可以使用多个 *.o 生成静态库
    $ gcc -c -o sub.o sub.c
    $ ar crs libsub.a sub.o
    
  • 生成可执行文件

    $ gcc -c -o main.o main.c
    $ gcc -o test main.o libsub.a 	# 如果 libsub.a 不在当前目录下,需要指定它的绝对或相对路径
    

3.3.2 运行

  • 不需要把静态库 libsub.a 放到板子上

    $ ./test
    Main fun!
    Sub fun!
    

你可能感兴趣的:(#,Linux应用开发,linux,C,gcc/gdb编译调试,so)