本文主要是编译与调试——GCC编译的相关笔记,若笔记中有错误或者不合适的地方,欢迎批评指正😃。
点击查看使用工具及版本
说明:此篇笔记若有更新,系统环境可能会发生变化,但大概的应该都是一样的。
PC端开发环境 Windows Windows11 Ubuntu Ubuntu20.04.6的64位版本 VMware® Workstation 17 Pro 17.0.0 build-20800274 终端软件 MobaXterm(Professional Edition v23.0 Build 5042 (license)) Win32DiskImager Win32DiskImager v1.0 Linux开发板环境 Linux开发板 正点原子 i.MX6ULL Linux 阿尔法开发板 uboot NXP官方提供的uboot,NXP提供的版本为uboot-imx-rel_imx_4.1.15_2.1.0_ga(使用的uboot版本为U-Boot 2016.03) linux内核 linux-4.15(NXP官方提供)
点击查看本文参考资料
点击查看相关文件下载
一、测试文件 这里准备几个文件用于测试,文件内容如下。
1. 单文件test.c 这个是不调用其他自定义的头文件和函数。LV01_GCC_COMPILE/00_compile/01_monofile · 苏木/imx6ull-app-demo - 码云 - 开源中国 (gitee.com)
1 2 3 4 5 6 7 #include <stdio.h> int main (int argc, char **argv) { printf ("Hello, world!\n" ); return 0 ; }
2. 多文件 main 这个测试文件包含多个文件,在main.c中调用另外两个c文件中的函数。LV01_GCC_COMPILE/00_compile/02_multifile · 苏木/imx6ull-app-demo - 码云 - 开源中国 (gitee.com)
2.1 main.c 1 2 3 4 5 6 7 8 9 10 11 #include <stdio.h> #include "func1.h" #include "func2.h" int main (int argc, const char *argv[]) { printf ("This is main file!\n" ); func1(); func2(); return 0 ; }
2.2 func1.c 1 2 3 4 5 6 #include <stdio.h> void func1 (void ) { printf ("This is func1.c file\n" ); }
2.3 func2.c 1 2 3 4 5 6 #include <stdio.h> void func2 (void ) { printf ("This is func2.c file\n" ); }
2.4 func1.h 1 2 3 4 5 6 #ifndef __FUNC1_H__ #define __FUNC1_H__ void func1 (void ) ;#endif
2.5 func2.h 1 2 3 4 5 6 #ifndef __FUNC2_H__ #define __FUNC2_H__ void func2 (void ) ;#endif
二、GCC 编译过程 1. 基本语法 我们先看几个编译命令,这里以单文件test.c为例:
1 2 3 4 5 6 7 gcc test.c -o test # 直接编译成可执行文件 # 以上命令等价于执行以下全部操作 gcc -E test.c -o test.i # 预处理,可理解为把头文件的代码汇总成C代码,把*.c转换得到*.i文件 gcc -S test.i -o test.s # 编译,可理解为把C代码转换为汇编代码,把*.i转换得到*.s文件 gcc -c test.s -o test.o # 汇编,可理解为把汇编代码转换为机器码,把*.s转换得到*.o,即目标文件 gcc test.o -o test # 链接,把不同文件之间的调用关系链接起来,把一个或多个*.o转换成最终的可执行文件
然后我们就会得到以下文件:
2. 编译过程分析 GCC 编译的流程有四个步骤:
【编译命令】
1 2 3 4 gcc -E test.c -o test.i # 预处理,得到预处理文件 *.i gcc -S test.i -o test.s # 编译, 得到汇编文件 *.s gcc -c test.s -o test.o # 汇编, 得到二进制文件 *.o gcc test.o -o test # 链接, 得到可执行文件 *.out
3. GCC编译的基本步骤 接下来我们对每个步骤进行分析,想要更为详细的了解,可以看这本书:
3.1 预处理( Pre-Processing )
使用GCC的参数“-E”,可以让编译器生成.i文件,参数“-o”,可以指定输出文件的名字。
预处理主要是进行宏的替换,例如 对文件包含#include 和预编译语句(如宏定义#define 等)进行处理,需要包含的进行包含,需要展开的宏进行展开等,所以在后续进行编译的时候是没有 include 和 define 的,所以 .i 文件里都是一些库函数的声明。 可理解为把头文件的代码、宏之类的内容转换成更纯粹的C代码,不过生成的文件以.i为后缀。
可以看这个文件:LV01_GCC_COMPILE/00_compile/01_monofile/test.i · 苏木/imx6ull-app-demo - 码云 - 开源中国 (gitee.com)
文件中以“#”开头的是注释,可看到有非常多的类型定义、函数声明被加入到文件中, 这些就是预处理阶段完成的工作,相当于它把原C代码中包含的头文件中引用的内容汇总到一处。 如果原C代码有宏定义,还可以更直观地看到它把宏定义展开成具体的内容(如宏定义代表的数字)。
3.2 编译( Compiling )
这个阶段编译器主要做词法分析、语法分析、语义分析等,在检查无错误后后,把代码翻译成汇编语言,生成 .s 文件。 在这个过程,即使我们调用了一个没有定义的函数,也不会报错。
可以看这个文件:LV01_GCC_COMPILE/00_compile/01_monofile/test.s · 苏木/imx6ull-app-demo - 码云 - 开源中国 (gitee.com)
汇编语言是跟平台相关的,由于本示例的GCC目标平台是x86,所以此处生成的汇编文件是x86的汇编代码。
3.3 汇编( Assembling )
汇编器 as 将 汇编语言文件翻译成机器语言。这一步是由汇编文件生成目标文件.o文件,每一个源文件都对应一个目标文件。GCC的参数“c”表示只编译(compile)源文件但不链接,会将源程序编译成目标文件(.o后缀)。计算机只认识0或者1,不懂得C语言,也不懂得汇编语言,经过编译汇编之后,生成的目标文件包含着机器代码,这部分代码就可以直接被计算机执行。
可以看这个文件:LV01_GCC_COMPILE/00_compile/01_monofile/test.o · 苏木/imx6ull-app-demo - 码云 - 开源中国 (gitee.com)
*.o是为了让计算机阅读的,所以不像前面生成的*.i和*.s文件直接使用字符串来记录, 如果直接使用编辑器打开,只会看到乱码。Linux下生成的*.o目标文件、*.so动态库文件以及\链接阶段生成最终的可执行文件都是elf格式的, 可以使用“readelf”工具来查看它们的内容。
然后我们可以看到如下内容:LV01_GCC_COMPILE/00_compile/01_monofile/test.o.txt · 苏木/imx6ull-app-demo - 码云 - 开源中国 (gitee.com) :
从readelf的工具输出的信息,可以了解到目标文件包含ELF头、程序头、节等内容, 对于*.o目标文件或*.so库文件,编译器在链接阶段利用这些信息把多个文件组织起来, 对于可执行文件,系统在运行时根据这些信息加载程序运行。
3.4 链接( Linking )
最后将每个源文件对应的.o文件以及各种库链接起来,生成一个可执行程序文件,得到最终的可执行文件。
这里只有一个test.c文件,但它调用了C标准代码库的printf函数, 所以链接器会把它和printf函数链接起来,生成最终的可执行文件。
还会有多个文件的情况,例如一个工程里包含了A和B两个代码文件,编译后生成了各自的A.o和B.o目标文件, 如果在代码A中调用了B中的某个函数fun,那么在A的代码中只要包含了fun的函数声明, 编译就会通过,而不管B中是否真的定义了fun函数(当然,如果函数声明都没有,编译也会报错)。 也就是说A.o和B.o目标文件在编译阶段是独立的,而在链接阶段, 链接过程需要把A和B之间的函数调用关系理顺,也就是说要告诉A在哪里能够调用到fun函数, 建立映射关系,所以称之为链接 。若链接过程中找不到fun函数的具体定义,则会链接报错。
链接分为两种:
动态链接,GCC编译时的默认选项。动态是指在应用程序运行时才去加载外部的代码库, 例如printf函数的C标准代码库*.so文件存储在Linux系统的某个位置, hello程序执行时调用库文件*.so中的内容,不同的程序可以共用代码库。 所以动态链接生成的程序比较小,占用较少的内存。
静态链接,链接时使用选项“–static”,它在编译阶段就会把所有用到的库打包到自己的可执行程序中。 所以静态链接的优点是具有较好的兼容性,不依赖外部环境,但是生成的程序比较大。
我们可以看一下两者的区别:
1 2 3 4 5 6 7 8 # test.o所在的目录执行如下命令 # 动态链接,生成名为test 的可执行文件 gcc test.o -o test_dynamic gcc test.c -o test_dynamic # 也可以直接使用C文件一步生成,与上面的命令等价 # 静态链接,使用--static参数,生成名为hello_static的可执行文件 gcc test.o -o test_static --static gcc test.c -o test_static --static # 也可以直接使用C文件一步生成,与上面的命令等价
从图中可以看到,使用动态链接生成的hello程序才17KB, 而使用静态链接生成的程序则高达852KB。在Ubuntu下,可以使用ldd工具查看动态文件的库依赖,尝试执行如下命令:
1 2 3 # 在 test 所在的目录执行如下命令 ldd test_dynamic ldd test_static
可以看到,动态链接生成的test_dynamic程序依赖于库文件linux-vdso.so.1、libc.so.6 以及ld-linux-x86-64.so.2,其中的libc.so.6就是我们常说的C标准代码库, 我们的程序中调用了它的printf库函数。静态链接生成的test_static没有依赖外部库文件。
4. 多文件编译 4.1 在同一个目录下 很多时候我们写代码不可能一个源文件写到尾,我们会根据功能,模块等将其写在不同的文件中,例如:LV01_GCC_COMPILE/00_compile/02_multifile · 苏木/imx6ull-app-demo - 码云 - 开源中国 (gitee.com)
需要注意的是一个C语言工程中,不管有多少个源文件,都一定知道能有一个main函数。
4.1.1 基本语法 有的时候我们在一个 C 文件中调用了另一个 C文件中的函数,但是这些文件必须只有一个 main 函数,他们编译的时候要一起编译。一般格式如下:
1 2 3 4 5 6 7 8 9 10 # 一般我们会在main函数所在的源文件目录下编译 # 一起编译链接 gcc file1.c file2.c ... main.c -o file_name # 分开编译,统一链接 gcc file1.c -o file1_name gcc file2.c -o file2_name ... gcc main.c -o file_name
【参数说明】
4.1.2 使用实例 如果说源 文件不在同一个目录下的话,我们编译需要加上 c 文件的路径,否则 GCC 编译的时候是找不到源文件的,例如如下文件目录结构(此处仅为展示目录结构,用的并不是笔记开头的文件):
1 2 3 4 5 6 . ├── func1.c ├── func1.h ├── func2.c ├── func2.h └── main.c
我们在 main.c 所在目录进行编译:
1 gcc func1.c func2.c main.c -o main
发现一切正常。
4.2 在不同目录中 例如:LV01_GCC_COMPILE/00_compile/03_multifile_dir_01 · 苏木/imx6ull-app-demo - 码云 - 开源中国 (gitee.com) 和LV01_GCC_COMPILE/00_compile/04_multifile_dir_02 · 苏木/imx6ull-app-demo - 码云 - 开源中国 (gitee.com)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 # 自己的C文件和自己的头文件一起 . ├── func1 │ ├── func1.c │ └── func1.h ├── func2 │ ├── func2.c │ └── func2.h └── main.c # C文件和h文件分开 . ├── func1 │ ├── func1.c │ └── func1.h ├── func2 │ ├── func2.c │ └── func2.h └── main.c
其实这两种情况都差不多。
4.2.1 我们编译一下
我们按照之前的方式编译:
1 gcc func1.c func2.c main.c -o main
发现找不到两个源文件,那我们加上路径好了:
1 gcc ./func1/func1.c ./func1/func2.c main.c -o main
然后又提示找不到头文件。
我们试一下另一个目录的情况:
1 gcc ./func/func1.c ./func/func2.c main.c -o main
发现也是一样的。
4.2.3 去哪里找头文件? 上面编译的过程中,我们发现,只要头文件跟main.c不在一个目录下,main.c在编译的时候必然报错,这是在预处理main.c的时候找不到这个头文件导致的,我们可以这样:
1 2 # include "./include/func1.h" # include "./include/func2.h"
这样修改源文件就不会有问题了,但是这是我们直接写出了头文件的位置,那要是我们优化工程,改变了头文件的位置,那岂不是要把所有的头文件都改一遍?那肯定不行,GCC为我们提供了 -I
选项,这个选项就指明了头文件的位置。
1 2 gcc ./func1/func1.c ./func2/func2.c main.c -o main -I./func1 -I./func2 gcc ./func/func1.c ./func/func2.c main.c -o main -I ./include
4.3 几个问题
答:系统目录,就是编译工具链里的某个 include 目录;也可以自己指定:编译时用-I dir
选项指定。 #include <xxx.h>
尖括号中的头文件就是在系统目录找。
答:系统目录,就是交叉编译工具链里的某个 lib 目录;也可以自己指定:链接时用 -L dir
选项指定。
答:系统目录,就是板子上的/lib、 /usr/lib 目录;也可以自己指定:运行程序用环境变量 LD_LIBRARY_PATH
指定。
【注意】当我们移植某些功能到arm开发板的时候,会有库文件,头文件等。由于运行时不需要头文件,所以头文件不用放到板子上
4.4 系统目录在哪? 我们在编译过程中除了语法问题,可能会遇到:
1 2 3 4 # 头文件问题 xxx.c:2:10: fatal error: xxxxx.h: 没有那个文件或目录 # 库文件问题 undefined reference to 'xxx'
在运行时有可能遇到:
1 2 error while loading shared libraries: libxxx.so: cannot open shared object file: No such file or directory
这其实就对应上面的三个问题,他们的答案都是系统目录,那么系统目录在哪? 执行下面命令确定目录:
1 echo 'main(){}'| gcc -E -v -
它会列出头文件目录、库目录(LIBRARY_PATH
)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 sumu@sumu-virtual-machine:~/1sharedfiles/linux_develop/imx6ull-app-demo/LV01_GCC_COMPILE/00_compile/multifile_dir_01$ echo 'main(){}'| gcc -E -v - Using built-in specs. COLLECT_GCC=gcc OFFLOAD_TARGET_NAMES=nvptx-none:hsa OFFLOAD_TARGET_DEFAULT=1 Target: x86_64-linux-gnu Configured with: ../src/configure -v --with-pkgversion='Ubuntu 9.4.0-1ubuntu1~20.04.2' --with-bugurl=file:///usr/share/doc/gcc-9/README.Bugs --enable-languages=c,ada,c++,go,brig,d,fortran,objc,obj-c++,gm2 --prefix=/usr --with-gcc-major-version-only --program-suffix=-9 --program-prefix=x86_64-linux-gnu- --enable-shared --enable-linker-build-id --libexecdir=/usr/lib --without-included-gettext --enable-threads=posix --libdir=/usr/lib --enable-nls --enable-clocale=gnu --enable-libstdcxx-debug --enable-libstdcxx-time=yes --with-default-libstdcxx-abi=new --enable-gnu-unique-object --disable-vtable-verify --enable-plugin --enable-default-pie --with-system-zlib --with-target-system-zlib=auto --enable-objc-gc=auto --enable-multiarch --disable-werror --with-arch-32=i686 --with-abi=m64 --with-multilib-list=m32,m64,mx32 --enable-multilib --with-tune=generic --enable-offload-targets=nvptx-none=/build/gcc-9-9QDOt0/gcc-9-9.4.0/debian/tmp-nvptx/usr,hsa --without-cuda-driver --enable-checking=release --build=x86_64-linux-gnu --host=x86_64-linux-gnu --target=x86_64-linux-gnu Thread model: posix gcc version 9.4.0 (Ubuntu 9.4.0-1ubuntu1~20.04.2) COLLECT_GCC_OPTIONS='-E' '-v' '-mtune=generic' '-march=x86-64' /usr/lib/gcc/x86_64-linux-gnu/9/cc1 -E -quiet -v -imultiarch x86_64-linux-gnu - -mtune=generic -march=x86-64 -fasynchronous-unwind-tables -fstack-protector-strong -Wformat -Wformat-security -fstack-clash-protection -fcf-protection ignoring nonexistent directory "/usr/local/include/x86_64-linux-gnu" ignoring nonexistent directory "/usr/lib/gcc/x86_64-linux-gnu/9/include-fixed" ignoring nonexistent directory "/usr/lib/gcc/x86_64-linux-gnu/9/../../../../x86_64-linux-gnu/include" # include "..." search starts here: # include <...> search starts here: /usr/lib/gcc/x86_64-linux-gnu/9/include /usr/local/include /usr/include/x86_64-linux-gnu /usr/include End of search list. # 1 "<stdin>" # 1 "<built-in>" # 1 "<command-line>" # 31 "<command-line>" # 1 "/usr/include/stdc-predef.h" 1 3 4 # 32 "<command-line>" 2 # 1 "<stdin>" main(){} COMPILER_PATH=/usr/lib/gcc/x86_64-linux-gnu/9/:/usr/lib/gcc/x86_64-linux-gnu/9/:/usr/lib/gcc/x86_64-linux-gnu/:/usr/lib/gcc/x86_64-linux-gnu/9/:/usr/lib/gcc/x86_64-linux-gnu/ LIBRARY_PATH=/usr/lib/gcc/x86_64-linux-gnu/9/:/usr/lib/gcc/x86_64-linux-gnu/9/../../../x86_64-linux-gnu/:/usr/lib/gcc/x86_64-linux-gnu/9/../../../../lib/:/lib/x86_64-linux-gnu/:/lib/../lib/:/usr/lib/x86_64-linux-gnu/:/usr/lib/../lib/:/usr/lib/gcc/x86_64-linux-gnu/9/../../../:/lib/:/usr/lib/ COLLECT_GCC_OPTIONS='-E' '-v' '-mtune=generic' '-march=x86-64'
后面我们会用到交叉编译工具,就是编译出可以在arm板子上跑的程序的工具,我们也来看一下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 sumu@sumu-virtual-machine:~/1sharedfiles/linux_develop/imx6ull-app-demo/LV01_GCC_COMPILE/00_compile/multifile_dir_01$ echo 'main(){}'| arm-linux-gnueabihf-gcc -E -v - 使用内建 specs。 COLLECT_GCC=arm-linux-gnueabihf-gcc 目标:arm-linux-gnueabihf 配置为:/home/tcwg-buildslave/workspace/tcwg-make-release/label/docker-trusty-amd64-tcwg-build/target/arm-linux-gnueabihf/snapshots/gcc-linaro-4.9-2017.01/configure SHELL=/bin/bash --with-mpc=/home/tcwg-buildslave/workspace/tcwg-make-release/label/docker-trusty-amd64-tcwg-build/target/arm-linux-gnueabihf/_build/builds/destdir/x86_64-unknown-linux-gnu --with-mpfr=/home/tcwg-buildslave/workspace/tcwg-make-release/label/docker-trusty-amd64-tcwg-build/target/arm-linux-gnueabihf/_build/builds/destdir/x86_64-unknown-linux-gnu --with-gmp=/home/tcwg-buildslave/workspace/tcwg-make-release/label/docker-trusty-amd64-tcwg-build/target/arm-linux-gnueabihf/_build/builds/destdir/x86_64-unknown-linux-gnu --with-gnu-as --with-gnu-ld --disable-libmudflap --enable-lto --enable-objc-gc --enable-shared --without-included-gettext --enable-nls --disable-sjlj-exceptions --enable-gnu-unique-object --enable-linker-build-id --disable-libstdcxx-pch --enable-c99 --enable-clocale=gnu --enable-libstdcxx-debug --enable-long-long --with-cloog=no --with-ppl=no --with-isl=no --disable-multilib --with-float=hard --with-mode=thumb --with-tune=cortex-a9 --with-arch=armv7-a --with-fpu=vfpv3-d16 --enable-threads=posix --enable-multiarch --enable-libstdcxx-time=yes --with-build-sysroot=/home/tcwg-buildslave/workspace/tcwg-make-release/label/docker-trusty-amd64-tcwg-build/target/arm-linux-gnueabihf/_build/sysroots/arm-linux-gnueabihf --with-sysroot=/home/tcwg-buildslave/workspace/tcwg-make-release/label/docker-trusty-amd64-tcwg-build/target/arm-linux-gnueabihf/_build/builds/destdir/x86_64-unknown-linux-gnu/arm-linux-gnueabihf/libc --enable-checking=release --disable-bootstrap --enable-languages=c,c++,fortran,lto --build=x86_64-unknown-linux-gnu --host=x86_64-unknown-linux-gnu --target=arm-linux-gnueabihf --prefix=/home/tcwg-buildslave/workspace/tcwg-make-release/label/docker-trusty-amd64-tcwg-build/target/arm-linux-gnueabihf/_build/builds/destdir/x86_64-unknown-linux-gnu 线程模型:posix gcc 版本 4.9.4 (Linaro GCC 4.9-2017.01) COLLECT_GCC_OPTIONS='-E' '-v' '-march=armv7-a' '-mtune=cortex-a9' '-mfloat-abi=hard' '-mfpu=vfpv3-d16' '-mthumb' '-mtls-dialect=gnu' /home/sumu/2software/gcc-linaro-4.9.4/bin/../libexec/gcc/arm-linux-gnueabihf/4.9.4/cc1 -E -quiet -v -imultilib . -imultiarch arm-linux-gnueabihf -iprefix /home/sumu/2software/gcc-linaro-4.9.4/bin/../lib/gcc/arm-linux-gnueabihf/4.9.4/ -isysroot /home/sumu/2software/gcc-linaro-4.9.4/bin/../arm-linux-gnueabihf/libc - -march=armv7-a -mtune=cortex-a9 -mfloat-abi=hard -mfpu=vfpv3-d16 -mthumb -mtls-dialect=gnu 忽略重复的目录“/home/sumu/2software/gcc-linaro-4.9.4/bin/../lib/gcc/../../lib/gcc/arm-linux-gnueabihf/4.9.4/include” 忽略不存在的目录“/home/sumu/2software/gcc-linaro-4.9.4/bin/../arm-linux-gnueabihf/libc/usr/local/include/arm-linux-gnueabihf” 忽略不存在的目录“/home/sumu/2software/gcc-linaro-4.9.4/bin/../arm-linux-gnueabihf/libc/usr/local/include” 忽略重复的目录“/home/sumu/2software/gcc-linaro-4.9.4/bin/../lib/gcc/../../lib/gcc/arm-linux-gnueabihf/4.9.4/include-fixed” 忽略重复的目录“/home/sumu/2software/gcc-linaro-4.9.4/bin/../lib/gcc/../../lib/gcc/arm-linux-gnueabihf/4.9.4/../../../../arm-linux-gnueabihf/include” 忽略不存在的目录“/home/sumu/2software/gcc-linaro-4.9.4/bin/../arm-linux-gnueabihf/libc/usr/include/arm-linux-gnueabihf” # include "..." 搜索从这里开始: # include <...> 搜索从这里开始: /home/sumu/2software/gcc-linaro-4.9.4/bin/../lib/gcc/arm-linux-gnueabihf/4.9.4/include /home/sumu/2software/gcc-linaro-4.9.4/bin/../lib/gcc/arm-linux-gnueabihf/4.9.4/include-fixed /home/sumu/2software/gcc-linaro-4.9.4/bin/../lib/gcc/arm-linux-gnueabihf/4.9.4/../../../../arm-linux-gnueabihf/include /home/sumu/2software/gcc-linaro-4.9.4/bin/../arm-linux-gnueabihf/libc/usr/include 搜索列表结束。 # 1 "<stdin>" # 1 "<built-in>" # 1 "<命令行>" # 1 "/home/sumu/2software/gcc-linaro-4.9.4/arm-linux-gnueabihf/libc/usr/include/stdc-predef.h" 1 3 4 # 1 "<命令行>" 2 # 1 "<stdin>" main(){} COMPILER_PATH=/home/sumu/2software/gcc-linaro-4.9.4/bin/../libexec/gcc/arm-linux-gnueabihf/4.9.4/:/home/sumu/2software/gcc-linaro-4.9.4/bin/../libexec/gcc/arm-linux-gnueabihf/:/home/sumu/2software/gcc-linaro-4.9.4/bin/../libexec/gcc/:/home/sumu/2software/gcc-linaro-4.9.4/bin/../lib/gcc/arm-linux-gnueabihf/4.9.4/../../../../arm-linux-gnueabihf/bin/ LIBRARY_PATH=/home/sumu/2software/gcc-linaro-4.9.4/bin/../lib/gcc/arm-linux-gnueabihf/4.9.4/:/home/sumu/2software/gcc-linaro-4.9.4/bin/../lib/gcc/arm-linux-gnueabihf/:/home/sumu/2software/gcc-linaro-4.9.4/bin/../lib/gcc/:/home/sumu/2software/gcc-linaro-4.9.4/bin/../lib/gcc/arm-linux-gnueabihf/4.9.4/../../../../arm-linux-gnueabihf/lib/:/home/sumu/2software/gcc-linaro-4.9.4/bin/../arm-linux-gnueabihf/libc/lib/:/home/sumu/2software/gcc-linaro-4.9.4/bin/../arm-linux-gnueabihf/libc/usr/lib/ COLLECT_GCC_OPTIONS='-E' '-v' '-march=armv7-a' '-mtune=cortex-a9' '-mfloat-abi=hard' '-mfpu=vfpv3-d16' '-mthumb' '-mtls-dialect=gnu'
三、GCC选项说明 1. 基本选项 在 GCC 编译的四个步骤中,我们单独进行每一步的时候都需要使用相应的参数选项:
选项
说明
-E
只激活预处理,不生成文件,需要把它重定向到一个输出文件里面。 例如,gcc -E test.c > test.txt
-S
只激活预处理和编译,就是指把文件编译成为汇编代码。 例如,gcc -S test.c # 将生成.s的汇编代码
-c
只激活预处理,编译,和汇编,也就是他只把程序做成obj文件。 例如,gcc -c test.c # 将生成.o的obj文件
-o
指定目标名称,缺省的时候,gcc 编译出来的文件默认名称是a.out。 例如,gcc test.c -o test
-v
显示制作 GCC 工具自身时的配置命令;同时显示编译器驱动程序、预处理器、
编译器的版本号。 例如:gcc -E test.c -o test.i -v
【说明】
使用 GCC 编译器编译 C 或者 C++ 程序,必须要经历这 4 个过程,就是预处理,编译,汇编和链接。但考虑在实际使用中,用户可能并不关心程序的执行结果,只想快速得到最终的可执行程序,因此 gcc 和 g++ 都对此需求做了支持。所以我们可以通过 gcc 命令一步直接得到可执行文件:
1 2 3 4 5 6 gcc -c test.c # 将会直接生成 a.out 可执行文件 gcc -c test.c -o test # 直接生成可执行文件,并且指定生成文件名为 test # 或者直接省略 -c gcc test.c # 将会直接生成 a.out 可执行文件 gcc test.c -o test # 直接生成可执行文件,并且指定生成文件名为 test
2. 链接器选项(Linker Option) 这一部分的选项是用于链接 OBJ 文件,输出可执行文件或库文件。
2.1 object-file-name 如果某些文件没有特别明确的后缀(a special recognized suffix),GCC 就认为他们是 OBJ 文件或库文件(根据文件内容,链接器能够区分 OBJ 文件和库文件)。
如果 GCC 执行链接操作,这些 OBJ 文件将成为链接器的输入文件。比如
1 gcc func.o main.o -o main
其中的 main.o、 func.o 就是输入的文件。
2.2 -llibrary(小写的L) 链接名为 library 的库文件。链接器在标准搜索目录中寻找这个库文件,库文件的真正名字是liblibrary.a
。搜索目录除了一些系统标准目录外,还包括用户以 -L
选项指定的路径。
一般说来用这个方法找到的文件是库文件──即由 OBJ 文件组成的归档文件(archive file)。链接器处理归档文件的方法是:扫描归档文件,寻找某些成员,这些成员的符号目前已被引用,不过还没有被定义。但是,如果链接器找到普通的 OBJ 文件,而不是库文件,就把这个 OBJ 文件按平常方式链接进来。
指定-l
选项和指定文件名的唯一区别是, -l
选项用lib
和.a
把 library 包裹起来,而且搜索一些目录。
2.2.1 链接库 链接器把多个二进制的目标文件(object file)链接成一个单独的可执行文件。在链接过程中,它必须把符号(变量名、函数名等一些列标识符)用对应的数据的内存地址(变量地址、函数地址等)替代,以完成程序中多个模块的外部引用。
标准库的大部分函数通常放在静态链接库 libc.a 中(文件名后缀 .a 代表“achieve”,译为“获取”),或者放在用于共享的动态链接 库 libc.so 中(文件名后缀 .so 代表“share object”,译为“共享对象”)。这些链接库一般位于 /lib/ 或 /usr/lib/,或者位于 GCC默认搜索的其他目录。
当使用 GCC 编译和链接程序时,GCC 默认会链接 libc.a 或者 libc.so,但是对于其他的库(例如非标准库、第三方库等),就需要手动添加。
2.2.2 基本格式 -l (小写的 L)参数,用来指定程序要链接的库(库文件在/lib、/usr/lib和/usr/local/lib下),-l 参数紧接着就是库名,一般使用格式如下:
lib_name :表示要链接的库名称(不包括库的前缀和后缀)。
【注意】 这个是小写的 L 。
2.2.3 使用实例 我们在使用 math.h 中的数学函数的时候,会产生函数未定义错误,标准头文件 <math.h> 对应的数学库默认不会被链接,如果没有手动将它添加进来,就会发生函数未定义错误。数学库的文件名是 libm.a。前缀 lib 和后缀 .a 是标准的,m 是库名称,GCC 会在 -l 选项后紧跟着的基本名称的基础上自动添加这些前缀、后缀 。如下程序LV01_GCC_COMPILE/00_compile/05_option_linker · 苏木/imx6ull-app-demo - 码云 - 开源中国 (gitee.com) ,调用了 math.h 中的cos函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 #include <stdio.h> #include <math.h> #define PI 3.14159265 int main (int argc, const char *argv[]) { double param; double result; param = 60.0 ; result = cos ( param * PI / 180.0 ); printf ("The cosine of %f degrees is %f.\n" , param, result ); return 0 ; }
然后我们在终端执行以下命令,我们先不加 -l 选项,编译程序,输入以下命令:
不出意外我们会看到以下打印信息:
1 2 3 /usr/bin/ld: /tmp/ccb61up1.o: in function `main': main.c:(.text+0x3e): undefined reference to `cos' collect2: error: ld returned 1 exit status
会发现有上边的报错信息出现,当我们加上 -l 选项的时候,再编译:
这个时候就不会有信息输出,因为我们的程序编译没有任何问题了。我们用ldd命令看一下这个文件的依赖库:
2.2.4 默认链接库? 即使不明显地使用-llibrary 选项,一些默认的库也被链接进去,可以使用-v 选项看到这点,还是使用刚才带有数学库的这个程序LV01_GCC_COMPILE/00_compile/05_option_linker · 苏木/imx6ull-app-demo - 码云 - 开源中国 (gitee.com) :
我们将会看到以下信息:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 Using built-in specs. COLLECT_GCC=gcc COLLECT_LTO_WRAPPER=/usr/lib/gcc/x86_64-linux-gnu/9/lto-wrapper OFFLOAD_TARGET_NAMES=nvptx-none:hsa OFFLOAD_TARGET_DEFAULT=1 Target: x86_64-linux-gnu Configured with: ../src/configure -v --with-pkgversion='Ubuntu 9.4.0-1ubuntu1~20.04.2' --with-bugurl=file:///usr/share/doc/gcc-9/README.Bugs --enable-languages=c,ada,c++,go,brig,d,fortran,objc,obj-c++,gm2 --prefix=/usr --with-gcc-major-version-only --program-suffix=-9 --program-prefix=x86_64-linux-gnu- --enable-shared --enable-linker-build-id --libexecdir=/usr/lib --without-included-gettext --enable-threads=posix --libdir=/usr/lib --enable-nls --enable-clocale=gnu --enable-libstdcxx-debug --enable-libstdcxx-time=yes --with-default-libstdcxx-abi=new --enable-gnu-unique-object --disable-vtable-verify --enable-plugin --enable-default-pie --with-system-zlib --with-target-system-zlib=auto --enable-objc-gc=auto --enable-multiarch --disable-werror --with-arch-32=i686 --with-abi=m64 --with-multilib-list=m32,m64,mx32 --enable-multilib --with-tune=generic --enable-offload-targets=nvptx-none=/build/gcc-9-9QDOt0/gcc-9-9.4.0/debian/tmp-nvptx/usr,hsa --without-cuda-driver --enable-checking=release --build=x86_64-linux-gnu --host=x86_64-linux-gnu --target=x86_64-linux-gnu Thread model: posix gcc version 9.4.0 (Ubuntu 9.4.0-1ubuntu1~20.04.2) COLLECT_GCC_OPTIONS='-v' '-mtune=generic' '-march=x86-64' /usr/lib/gcc/x86_64-linux-gnu/9/cc1 -quiet -v -imultiarch x86_64-linux-gnu main.c -quiet -dumpbase main.c -mtune=generic -march=x86-64 -auxbase main -version -fasynchronous-unwind-tables -fstack-protector-strong -Wformat -Wformat-security -fstack-clash-protection -fcf-protection -o /tmp/ccf6qxWl.s GNU C17 (Ubuntu 9.4.0-1ubuntu1~20.04.2) version 9.4.0 (x86_64-linux-gnu) compiled by GNU C version 9.4.0, GMP version 6.2.0, MPFR version 4.0.2, MPC version 1.1.0, isl version isl-0.22.1-GMP GGC heuristics: --param ggc-min-expand=100 --param ggc-min-heapsize=131072 ignoring nonexistent directory "/usr/local/include/x86_64-linux-gnu" ignoring nonexistent directory "/usr/lib/gcc/x86_64-linux-gnu/9/include-fixed" ignoring nonexistent directory "/usr/lib/gcc/x86_64-linux-gnu/9/../../../../x86_64-linux-gnu/include" # include "..." search starts here: # include <...> search starts here: /usr/lib/gcc/x86_64-linux-gnu/9/include /usr/local/include /usr/include/x86_64-linux-gnu /usr/include End of search list. GNU C17 (Ubuntu 9.4.0-1ubuntu1~20.04.2) version 9.4.0 (x86_64-linux-gnu) compiled by GNU C version 9.4.0, GMP version 6.2.0, MPFR version 4.0.2, MPC version 1.1.0, isl version isl-0.22.1-GMP GGC heuristics: --param ggc-min-expand=100 --param ggc-min-heapsize=131072 Compiler executable checksum: 01da938ff5dc2163489aa33cb3b747a7 COLLECT_GCC_OPTIONS='-v' '-mtune=generic' '-march=x86-64' as -v --64 -o /tmp/ccfeejDk.o /tmp/ccf6qxWl.s GNU汇编版本 2.34 (x86_64-linux-gnu) 使用BFD版本 (GNU Binutils for Ubuntu) 2.34 COMPILER_PATH=/usr/lib/gcc/x86_64-linux-gnu/9/:/usr/lib/gcc/x86_64-linux-gnu/9/:/usr/lib/gcc/x86_64-linux-gnu/:/usr/lib/gcc/x86_64-linux-gnu/9/:/usr/lib/gcc/x86_64-linux-gnu/ LIBRARY_PATH=/usr/lib/gcc/x86_64-linux-gnu/9/:/usr/lib/gcc/x86_64-linux-gnu/9/../../../x86_64-linux-gnu/:/usr/lib/gcc/x86_64-linux-gnu/9/../../../../lib/:/lib/x86_64-linux-gnu/:/lib/../lib/:/usr/lib/x86_64-linux-gnu/:/usr/lib/../lib/:/usr/lib/gcc/x86_64-linux-gnu/9/../../../:/lib/:/usr/lib/ COLLECT_GCC_OPTIONS='-v' '-mtune=generic' '-march=x86-64' /usr/lib/gcc/x86_64-linux-gnu/9/collect2 -plugin /usr/lib/gcc/x86_64-linux-gnu/9/liblto_plugin.so -plugin-opt=/usr/lib/gcc/x86_64-linux-gnu/9/lto-wrapper -plugin-opt=-fresolution=/tmp/ccoTvGJk.res -plugin-opt=-pass-through=-lgcc -plugin-opt=-pass-through=-lgcc_s -plugin-opt=-pass-through=-lc -plugin-opt=-pass-through=-lgcc -plugin-opt=-pass-through=-lgcc_s --build-id --eh-frame-hdr -m elf_x86_64 --hash-style=gnu --as-needed -dynamic-linker /lib64/ld-linux-x86-64.so.2 -pie -z now -z relro /usr/lib/gcc/x86_64-linux-gnu/9/../../../x86_64-linux-gnu/Scrt1.o /usr/lib/gcc/x86_64-linux-gnu/9/../../../x86_64-linux-gnu/crti.o /usr/lib/gcc/x86_64-linux-gnu/9/crtbeginS.o -L/usr/lib/gcc/x86_64-linux-gnu/9 -L/usr/lib/gcc/x86_64-linux-gnu/9/../../../x86_64-linux-gnu -L/usr/lib/gcc/x86_64-linux-gnu/9/../../../../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/9/../../.. /tmp/ccfeejDk.o -lm -lgcc --push-state --as-needed -lgcc_s --pop-state -lc -lgcc --push-state --as-needed -lgcc_s --pop-state /usr/lib/gcc/x86_64-linux-gnu/9/crtendS.o /usr/lib/gcc/x86_64-linux-gnu/9/../../../x86_64-linux-gnu/crtn.o COLLECT_GCC_OPTIONS='-v' '-mtune=generic' '-march=x86-64'
可以看见,还链接了启动文件 Scrt1.o、 crti.o、crtend.o 、 crtn.o,还有一些库文件(-lgcc -lgcc_s -lc)。
2.3 -nostartfiles 不链接系统标准启动文件,而标准库文件仍然正常使用,还是这个实例LV01_GCC_COMPILE/00_compile/05_option_linker · 苏木/imx6ull-app-demo - 码云 - 开源中国 (gitee.com) :
1 gcc -v -nostartfiles main.c -lm
我们将会看到以下打印信息:
1 2 3 4 # ...... /usr/lib/gcc/x86_64-linux-gnu/9/collect2 -plugin /usr/lib/gcc/x86_64-linux-gnu/9/liblto_plugin.so -plugin-opt=/usr/lib/gcc/x86_64-linux-gnu/9/lto-wrapper -plugin-opt=-fresolution=/tmp/cc8zjNED.res -plugin-opt=-pass-through=-lgcc -plugin-opt=-pass-through=-lgcc_s -plugin-opt=-pass-through=-lc -plugin-opt=-pass-through=-lgcc -plugin-opt=-pass-through=-lgcc_s --build-id --eh-frame-hdr -m elf_x86_64 --hash-style=gnu --as-needed -dynamic-linker /lib64/ld-linux-x86-64.so.2 -pie -z now -z relro -L/usr/lib/gcc/x86_64-linux-gnu/9 -L/usr/lib/gcc/x86_64-linux-gnu/9/../../../x86_64-linux-gnu -L/usr/lib/gcc/x86_64-linux-gnu/9/../../../../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/9/../../.. /tmp/ccgRZiNF.o -lm -lgcc --push-state --as-needed -lgcc_s --pop-state -lc -lgcc --push-state --as-needed -lgcc_s --pop-state /usr/bin/ld: 警告: 无法找到项目符号 _start; 缺省为 0000000000001050 COLLECT_GCC_OPTIONS='-v' '-nostartfiles' '-mtune=generic' '-march=x86-64'
可以看见启动文件 Scrt1.o、 crti.o、 crtend.o 、 crtn.o 没有被链接进去。但是最终还是会生成a.out文件。需要说明的是,对于一般应用程序,这些启动文件是必需的,这里仅是作为例子(这样编译出来的 a.out 文件无法执行)。在编译 bootloader、内核时,将用到这个选项。
2.4 -nostdlib 不链接系统标准启动文件和标准库文件,只把指定的文件传递给链接器。这个选项常用于编译内核、 bootloader 等程序,它们不需要启动文件、标准库文件。 还是以LV01_GCC_COMPILE/00_compile/05_option_linker · 苏木/imx6ull-app-demo - 码云 - 开源中国 (gitee.com) 这个文件为例:
1 gcc -v -nostdlib main.c -lm
将会看到以下信息:
1 2 3 4 5 # ...... /usr/lib/gcc/x86_64-linux-gnu/9/collect2 -plugin /usr/lib/gcc/x86_64-linux-gnu/9/liblto_plugin.so -plugin-opt=/usr/lib/gcc/x86_64-linux-gnu/9/lto-wrapper -plugin-opt=-fresolution=/tmp/ccWOd8Ds.res --build-id --eh-frame-hdr -m elf_x86_64 --hash-style=gnu --as-needed -dynamic-linker /lib64/ld-linux-x86-64.so.2 -pie -z now -z relro -L/usr/lib/gcc/x86_64-linux-gnu/9 -L/usr/lib/gcc/x86_64-linux-gnu/9/../../../x86_64-linux-gnu -L/usr/lib/gcc/x86_64-linux-gnu/9/../../../../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/9/../../.. /tmp/ccmtDNcr.o -lm /usr/bin/ld: /tmp/ccmtDNcr.o: undefined reference to symbol 'printf@@GLIBC_2.2.5' /usr/bin/ld: /lib/x86_64-linux-gnu/libc.so.6: error adding symbols: DSO missing from command line collect2: error: ld returned 1 exit status
出现了一大堆错误,因为 printf 等函数是在库文件中实现的。在编译bootloader、内核时,用到这个选项──它们用到的很多函数是自包含的。
2.5 -static 在支持动态链接(dynamic linking)的系统上,阻止链接共享库。仍以 程序为例,是否使用-static 选项编译出来的可执行程序大小相差很大,我们以这个为例LV01_GCC_COMPILE/00_compile/05_option_linker · 苏木/imx6ull-app-demo - 码云 - 开源中国 (gitee.com) :
1 2 gcc -static main.c -o a_static.out -lm gcc main.c -o a_no_static.out -lm
当不使用-static 编译文件时,程序执行前要链接共享库文件,所以还需要将共享库文件放入文件系统中。
2.6 -shared 生成一个共享 OBJ 文件,它可以和其他 OBJ 文件链接产生可执行文件。只有部分系统支持该选项。当不想以源代码发布程序时,可以使用-shared 选项生成库文件。这个就是链接库的制作方法啦,后面还会详细去学习。
2.7 -Xlinker option 把选项 option 传递给链接器。可以用来传递系统特定的链接选项, GCC 无法识别这些选项。如果需要传递携带参数的选项,必须使用两次-Xlinker
,一次传递选项,另一次传递其参数。例如,如果传递-assert definitions
,这样写:
1 -Xlinker -assert -Xlinker definitions
而不能写成
1 -Xlinker "-assert definitions"
因为这样会把整个字符串当做一个参数传递,显然这不是链接器期待的。
2.8 -Wl,option 把选项 option 传递给链接器。如果 option 中含有逗号,就在逗号处分割成多个选项。链接器通常是通过 gcc、 arm-linux-gcc 等命令间接启动的,要向它传入参数时,参数前面加上-Wl,
。
3. 目录选项(Directory Option) 下列选项指定搜索路径,用于查找头文件,库文件,或编译器的某些成员。 Directory Options (Using the GNU Compiler Collection (GCC))
3.1 -ldir 3.1.1 基本格式 -I (大写的 i )选项用于将指定头文件路径添加到 GCC 的头文件搜索路径中,一般使用格式如下:
1 2 -Idir # 单个路径 -Idir1 -Idir2 ... # 多个路径,每个路径都要有 -I ,不同的 -I 之间用空格分隔开
在头文件的搜索路径列表中添加 dir 目录。头文件的搜索方法为:如果以#include < >
包含文件,则只在标准库目录开始搜索(包括使用-Idir 选项定义的目录);如果以#include " "
包含文件,则先从用户的工作目录开始搜索,再搜索标准库目录。
【注意】
(1)-I 和 dir 直接可以有空格,也可以没有空格.
(2)这个参数是大写的 i ,有些字体下,看着比较容易与另一个字母混淆。
3.1.2 使用实例
1 2 3 4 5 6 7 8 . ├── dir1 │ └── func1.h ├── dir2 │ └── func2.h ├── func1.c ├── func2.c ├── main.c
我们直接使用如下命令编译:
1 gcc func1.c func2.c main.c -o main
这样会直接报错:
我们加上 -I 选项:
1 gcc func1.c func2.c main.c -o main -I dir1 -I dir2
这个时候就可以正常编译啦,编译完成后会生成 main 可执行文件,我们直接在终端执行 ./main,则会看到如下输出:
1 2 3 This is main file! This is func1.c file This is func2.c file
3.2 -I- 分割包含路径。此选项已弃用。下面这段是资料上写的,反正我也没看懂,平时编程也没见过,就简单了解下吧:
任何在-I-
前面用-I
选项指定的搜索路径只适用于 #include "file"
这种情况;它们不能用来搜索#include <file>
包含的头文件。如果用-I
选项指定的搜索路径位于-I-
选项后面,就可以在这些路径中搜索所有的#include
指令(一般说来-I 选项就是这么用的)。还有, -I-
选项能够阻止当前目录(存放当前输入文件的地方)成为搜索#include "file"
的第一选择。
-I-
不影响使用系统标准目录,因此, -I-
和-nostdinc
是不同的选项。
3.3 -Ldir 放在 /lib 和 /usr/lib 和 /usr/local/lib 里的库直接用 -l (小写L)参数就能链接了,但如果库文件没放在这三个目录里,而是放在其他目录里,这时我们只用-l参数的话,链接还是会出错,GCC在链接的时候并不知道除了这些地方的库外要链接的库在哪里。
3.3.1 基本格式 -L 选项可以为GCC增加一个搜索链接库的目录,当我们生成了自己的链接库的时候,我们可以用这个参数将其添加到GCC的搜索路径下:
1 2 3 4 5 # 单个路径 gcc -L<dir> # 多个路径 gcc -L<dir1> -L<dir2> ... gcc -L<dir1>:<dir2>: ...
【注意】 要是有多个链接库的目录要添加的话,可以使用多个 -L 选项,或者就是在一个-L选项中用冒号分隔不同的路径。
3.3.2 使用实例 3.3.2.1 文件准备 我们先进行一个文件的准备,准备完成后,所有文件目录结构如下:
1 2 3 4 5 6 7 8 9 . ├── include │ ├── func1.h │ └── func2.h ├── lib ├── main.c └── src ├── func1.c └── func2.c
3.3.2.2 制作静态库 由于要链接库,所以要先只做静态库,后边有笔记专门记录静态库和动态库的制作,这里就是记录一个过程。
1 2 3 4 5 6 7 cd src/ # 进入func1.c 和 func2.c 源文件所在目录 gcc -c func1.c -o func1.o # 编译func1.c,生成 func1.o gcc -c func2.c -o func2.o # 编译func2.c,生成 func2.o ar -rsv libfunc.a func1.o func2.o # 打包成静态库 mv libfunc.a ../lib/ # 拷贝到库文件目录下
3.3.2.3 编译主函数源文件 此时我们开始编译主函数,需要注意的是,我们即便做了静态链接库,但是还是需要指定头文件的位置:
1 gcc main.c -o main -I include/
我们发现是在 ld 的时候出现问题,这说明我们没有链接相应的库,我们加上 -l 选项:
1 gcc main.c -o main -I include -lfunc
此时提示,找不到链接库:
这时候我们为 GCC 指条明路:
1 gcc main.c -o main -I include -L lib -lfunc
这个时候,就会发现,没有报错,正常生成了可执行文件。
3.4 -Bprefix 这个选项指出在何处寻找可执行文件,库文件,以及编译器自己的数据文件。
编译器驱动程序需要使用某些工具,比如: cpp
, cc1
(或 C++的cc1plus
),as
和ld
。它把 prefix 当作欲执行的工具的前缀,这个前缀可以用来指定目录,也可以用来修改工具名字。
对于要运行的工具,编译器驱动程序首先试着加上-B
前缀(如果存在),如果没有找到文件,或没有指定-B
选项,编译器接着会试验两个标准前缀/usr/lib/gcc/
和/usr/local/lib/gcc-lib/
。如果仍然没能够找到所需文件,编译器就在PATH
环境变量指定的路径中寻找没加任何前缀的文件名。如果有需要,运行时(run-time)支持文件libgcc.a
也在-B
前缀的搜索范围之内。如果这里没有找到,就在上面提到的两个标准前缀中寻找,仅此而已。如果上述方法没有找到这个文件,就不链接它了。多数情况的多数机器上,libgcc.a
并非必不可少。
可以通过环境变量 GCC_EXEC_PREFIX
获得近似的效果;如果定义了这个变量 , 其值就和上面说的一样被用作前缀。如果同时指定了 -B
选 项 和GCC_EXEC_PREFIX
变量,编译器首先使用-B
选项,然后才尝试环境变量值。
4. 警告选项(Warning Option)-Wall 这个选项基本打开了所有需要注意的警告信息,比如没有指定类型的声明、在声明之前就使用的函数、局部变量除了声明就没再使用等。
有时候有的警告虽然对程序没有坏的影响,但是有些警告需要加以关注,比如类型匹配的警告等。
5. 调试选项(Debugging Option)-g 以操作系统的本地格式(stabs, COFF, XCOFF,或 DWARF)产生调试信息,GDB 能够使用这些调试信息。在大多数使用 stabs 格式的系统上, -g
选项加入只有 GDB 才使用的额外调试信息。可以使用下面的选项来生成额外的信息: -gstabs+
, -gstabs
, -gxcoff+
, -gxcoff
, -gdwarf+
或-gdwarf
,具体用法可以参考 GCC 手册。
6. 优化选项(Optimization Option) gcc 提供了为了满足用户不同程度的的优化需要,提供了近百种优化选项,用来对 编译时间,目标文件长度,执行效率 这个三维模型进行不同的取舍和平衡。优化的方法不一而足,总体上将有以下几类:
(1)精简操作指令;
(2)尽量满足cpu的流水操作;
(3)通过对程序行为地猜测,重新调整代码的执行顺序;
(4)充分使用寄存器;
(5)对简单的调用进行展开等等。
如果指定了多个-O 选项,不管带不带数字,生效的是最后一个选项。在一般应用中,经常使用-O2
选项。
6.1 -O0 不做任何优化,这是默认的编译选项。
6.2 -O 或-O1 优化:对于大函数,优化编译的过程将占用稍微多的时间和相当大的内存。不使用-O
或-O1
选项的目的是减少编译的开销,使编译结果能够调试、语句是独立的:如果在两条语句之间用断点中止程序,可以对任何变量重新赋值,或者在函数体内把程序计数器指到其他语句,以及从源程序中精确地获取我们所期待的结果。
不使用-O
或-O1
选项时,只有声明了 register 的变量才分配使用寄存器。
使用了-O
或-O1
选项,编译器会试图减少目标码的大小和执行时间。如果指定了-O
或-O1
选项,, -fthread-jumps
和-fdefer-pop
选项将被打开。在有 delay slot 的机器上, -fdelayed-branch
选项将被打开。在即使没有帧指针 (frame pointer)也支持调试的机器上, -fomit-framepointer
选项将被打开。某些机器上还可能会打开其他选项。
6.3 -O2 多优化一些。除了涉及空间和速度交换的优化选项,执行几乎所有的优化工作。例如不进行循环展开(loop unrolling)和函数内嵌(inlining)。
和-O
或-O1
选项比较,这个选项增加了编译时间,也提高了生成代码的运行效果。
6.4 -O3 优化的更多。除了打开-O2
所做的一切,它还打开了-finline-functions
选项。
7. 预处理器选项(Preprocessor Options) 可以参考gcc9.5的文档:Preprocessor Options (Using the GNU Compiler Collection (GCC))
7.1 -D 有时候我们想在 c 源文件中使用 Makefile 中定义的某些变量,根据变量的取值做出不同的处理,比如 debug 开关、版本信息等,这时候我们可以通过 gcc 的 -D 选项来满足这一需求,它等同于在 C 文件中通过 #define 语句定义一个宏。
常用的场景就是用 -DDEBUG 定义DEBUG宏,然后文件中有 DEBUG 宏部分的相关信息,用个 -DDEBUG 在编译的时候来选择开启或关闭 DEBUG 。
7.1.1 基本格式 GCC的 -D 选项就相当于是在 C 语言中定义了一个 #define 宏,一般格式如下:
1 gcc -D name[=definition]
name:就是表示定义name这个宏,可以与-D之间没有空格
[=definition] :表示定义的宏的值,这个是可选的,如果不写的话,宏的值默认是1 。
【注意】 要是需要给宏一个值的话, 等号两端不能有空格。
7.1.1 GCC实例
1 2 3 4 5 6 7 8 9 10 11 12 #include <stdio.h> int main (int argc, const char *argv[]) { printf ("This is main file!\n" ); #ifdef TEST printf ("TEST=%d\n" , TEST); #else printf ("TEST is not defined!\n" ); #endif return 0 ; }
我们在终端执行以下命令编译程序,然后执行,会看到如下信息:
可以看到如下打印信息:
1 2 3 # 以下为执行可执行程序的输出信息 This is main file! TEST is not defined!
然后我们加上 -D 选项,定义一个宏:
1 2 gcc main.c -D TEST ./a.out
可以看到如下打印信息:
1 2 3 # 以下为执行可执行程序的输出信息 This is main file! TEST=1
然后我们加上 -D 选项,定义一个带有值的宏:
1 2 3 4 5 hk@vm:~/1sharedfiles/test$ gcc main.c -D TEST=5 hk@vm:~/1sharedfiles/test$ ./a.out # 以下为执行可执行程序的输出信息 This is main file! TEST=5
7.1.2 Makefile实例 这部分是后来新加的,make相关知识可以看后边的笔记。
1 2 3 4 5 6 7 8 9 10 11 12 #include <stdio.h> int main (int argc, const char *argv[]) { printf ("This is main file!\n" ); #ifdef TEST printf ("TEST=%d\n" , TEST); #else printf ("TEST is not defined!\n" ); #endif return 0 ; }
我们编写一个 Makefile 文件,内容如下:
1 2 3 TEST = 8 all: gcc main.c -Wall -o main -D TEST=$(TEST)
我们编译,并执行可执行文件,结果如下:
参考资料:
编译器:原理与技术的奥秘-阿里云开发者社区 (aliyun.com)