主要参考野火电子:http://doc.embedfire.com/linux/imx6/base/zh/latest/index.html
GCC
编译工具链GCC
编译工具链(toolchain
)是指以 GCC
编译器为核心的一整套工具,用于把源代码转化成可执行应用程序。它主要包含以下三部分内容:
gcc-core :即GCC编译器,用于完成预处理和编译过程,例如把C代码转换成汇编代码。
Binutils :除GCC编译器外的一系列小工具包括了链接器 ld
,汇编器 as
、目标文件格式查看器 readelf
等。
glibc :包含了主要的 C
语言标准函数库,C
语言中常常使用的打印函数 printf
、malloc
函数就在 glibc
库中。
在很多场合下会直接用 GCC
编译器 来指代整套 GCC
编译工具链。
GCC
编译器GCC
(GNU Compiler Collection
)是由 GNU
开发的编程语言编译器。 GCC
最初代表 “GNU C Compiler”
,当时只支持C语言。 后来又扩展能够支持更多编程语言,包括 C++
、Fortran
和 Java
等。 因此,GCC
也被重新定义为 “GNU Compiler Collection”
,成为历史上最优秀的编译器, 其执行效率与一般的编译器相比平均效率要高 20%~30%。GCC的官网地址
在 Ubuntu
系统下系统默认已经安装好 GCC
编译器,可以通过命令查看 Ubuntu
系统中 GCC
编译器的版本及安装路径:
Binutils
工具集Binutils
(bin utility
),是 GNU
二进制工具集,通常跟 GCC
编译器一起打包安装到系统。Binutils官方说明
在进行程序开发的时候通常不会直接调用这些工具,而是在使用 GCC
编译指令的时候由 GCC
编译器间接调用。下面是其中一些常用的工具:
as
:汇编器,把汇编语言代码转换为机器码(目标文件)。ld
:链接器,把编译生成的多个目标文件组织成最终的可执行程序文件。readelf
:可用于查看目标文件或可执行程序文件的信息。nm
: 可用于查看目标文件中出现的符号。objcopy
: 可用于目标文件格式转换,如 .bin
转换成 .elf
、.elf
转换成 .bin
等。objdump
:可用于查看目标文件的信息,最主要的作用是反汇编。size
:可用于查看目标文件不同部分的尺寸和总尺寸,例如代码段大小、数据段大小、使用的静态内存、总大小等。系统默认的 Binutils
工具集位于 /usr/bin
目录下。
glibc
库glibc
库是 GNU
组织为 GNU
系统以及 Linux
系统编写的 C
语言标准库,因为绝大部分 C
程序都依赖该函数库,该文件甚至会直接影响到系统的正常运行,例如常用的文件操作函数 read
、write
、open
,打印函数 printf
、动态内存申请函数 malloc
等。
在 Ubuntu
系统下,libc.so.6
是 glibc
的库文件,可直接执行该库文件查看版本:
GCC
编译过程一个 C/C++
文件要经过预处理、编译、汇编和链接等 4 步才能变成可执行文件。在日常中通常使用“编译”统称这 4 个步骤。
GCC
使用的命令语法$ gcc [option] 文件名
常用选项:
-o
:小写字母“o”,指定生成的可执行文件的名字,不指定的话生成的可执行文件名为a.out。-E
:只进行预处理,既不编译,也不汇编。-S
:只编译,不汇编。-c
:编译并汇编,但不进行链接。-g
:生成的可执行文件带调试信息,方便使用gdb进行调试。-Ox
:大写字母“O”加数字,设置程序的优化等级,如“-O0”“-O1” “-O2” “-O3”, 数字越大代码的优化等级越高,编译出来的程序一般会越小,但有可能会导致程序不正常运行。I
:指定头文件目录L
:指定链接时库文件目录l
:指定链接哪一个库文件GCC
编译工具链在编译一个 C
源文件时需要经过以下 4 步:
include
)、 预编译语句(如宏定义define
等)进行展开,生成 .i
文件。 可理解为把头文件的代码、宏之类的内容转换成更纯粹的C代码,不过生成的文件以 .i
为后缀。.i
文件通过编译成为汇编语言,生成 .s
文件,即把代码从C语言转换成汇编语言,这是 GCC
编译器完成的工作。.o
文件,每一个源文件都对应一个目标文件。即把汇编语言的代码转换成机器码,这是 as
汇编器完成的工作。.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
命令
# 预处理,可理解为把头文件的代码汇总成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;
}
命令
# 编译,可理解为把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
命令
# 汇编,可理解为把汇编代码转换为机器码,把*.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
# 省略了很多
命令
# 动态链接
# 生成名为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 # 不是动态可执行文件
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 */
一起编译,链接
$ 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
制作动态库
# 可以使用多个 *.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 指定库文件目录
把 libusb.so
放到 PC
或板子上的 /lib
目录,然后就可以运行 test
程序。
如果不想把 libusb.so
放到 /lib
,也可以放在某个目录比如 /a
,然后如下执行
$ export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/a
$ ./test
Main fun!
Sub fun!
制作静态库
# 可以使用多个 *.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 不在当前目录下,需要指定它的绝对或相对路径
不需要把静态库 libsub.a
放到板子上
$ ./test
Main fun!
Sub fun!