三、GCC编译参数
1. 常用编译选项参数汇总
选项参数 | 作用解析 |
-E | 预处理生成 .i 文件 |
-S | 编译生成 .s 汇编文件 |
-c | 汇编生成 .o 目标文件 |
-o | 指定目标文件 |
-O | 优化选项,有1-3级 |
-I (大写i) | 指定包含头文件的路径(绝对、相对路径都可) |
-l (小写L) | 指定库名,libxxx.a或libxxx.so |
-L | 包含的库路径 |
-g | 生成调试信息,用于gdb调试,如果不加这个选项无法进行gdb调试 |
-Wall | 显示更多警告信息 |
-D | 指定宏 |
-lstdc++ | 编译C++源代码 |
-E/-S/-c 在上面已经介绍完毕,下面介绍剩下的选项参数。
2. 使用方法介绍
准本工作,首先准备一个hello.c文件,这是一个单独的文件
然后准备一个main.c和一个test.h一个test.c文件,main.c文件和test.c文件放在main目录下,test.h放在header目录下,目录结构如下
文件内容如下
这三个文件的关系是,main.c调用了test.h中的函数,test.c实现了test.h中的函数。下面将使用这三个文件进行演示。
(1)-o 指定目标文件
如果使用GCC编译且不加任何选项的时候,默认会生成一个 a.out 的可执行文件
如果加上 -o 选项就可以自己指定可执行文件名甚至是后缀
这些绿色的文件都是可执行的,前面已经多次强调过,可执行文件和后缀没有关系,不过我们一般会指定为和源文件同名,无后缀的这种格式,即这里的 hello 文件。
(2)-O 优化选项
优化选项,不写就是默认不优化,1-3优化等级越来越高,但实际上并非优化等级越高就越好。
- O0:关闭所有优化选项,这是编译器默认的编译选项。
- O1:基本优化,编译器会尝试减小生成代码的尺寸,以及缩短执行时间,但并不执行需要占用大量编译时间的优化。
- O2:包含-O1的优化并增加了不需要在目标文件大小和执行速度上进行折衷的优化。编译器不执行循环展开以及函数内联。会尝试更多的寄存器级的优化以及指令级的优化,它会在编译期间占用更多的内存和编译时间。 大多数情况下,推荐使用 O2 这一级优化选项就足够了。
- O3:最高的优化级别,它会使用更多的编译时间,并且会增大二进制文件的体积并让他们更消耗内存。O3 级优化除了会打开所有 -O2 的优化选项外增加 -finline-functions 、-funswitch-loops 、-frename-registers 、-fweb 等优化选项(这些优化选项随着优化级别的增加会添加更多优化选项,每个选项都会完成一定的优化内容,这里就不深入探讨了,只需要了解 O1-O3 的使用即可)。这一级优化编译时间最长,生成的目标文件也更大,有时性能不增反而降低,甚至产生不可预知的问题或错误,所以大多数情况下不推荐使用。
- Os:其实还有一个 Os ,它使用了所有 -O2 的优化选项,但又不会缩减代码的尺寸大小,姑且把它算在第二三级之间吧。
(3)-I 指定包含头文件路径及头文件引入的两种方法
我们编译一下前面准备好的main.c和test.c
可以看到,编译错误,找不到头文件test.h,这时有人可能会很奇怪,在main.c中和test.c中已经包含头文件了呀,为啥会找不到呢?其实,这是因为这个头文件和main.c、test.c不在同一个目录下。我们应该知道,在包含头文件的时候,标准库文件一般用尖括号 <> ,编译器回到默认的目录下寻找这些.h文件,如果是自己写的头文件,要用双引号 “” 去包含,编译器会在当前目录(源文件所在目录)进行查找。也就是说,出现这个错误的原因是,gcc编译器找不到test.h这个文件,它不知道这个文件在哪个目录下。
- #include <>:将指定文件引入到当前文件,搜索策略为,直接在编译器指定的路径处开始搜索,如果找不到被引入文件,则程序报错。因此系统提供的头文件推荐使用这种方式引入。如果是集成开发环境,比如VS,这个默认路径一般在VS安装目录下的一个名为 include 的路径下。在Linux中,一般默认路径是 /usr/include 或 /usr/lib 下的目录。
- #include “”:将指定文件引入到当前文件,搜索策略为,首先在运行程序所在的目录处进行搜索,搜索失败后再到编译器指定的路径处搜索,如果仍然搜索失败,则直接报错。因此,用户自定义头文件必须用这种方式引入,系统提供的头文件也可以使用这种方式,但是会增加没必要的搜索,所以不推荐。
那么上面问题的解决方法就是加 -I 选项,可以使用相对路径或决定路径:
相对路径
gcc main.c test.c -o main -I ../header/
绝对路径
gcc main.c test.c -o main -I /home/qq/dm/dm_gcc/header/
(4)-l (小写L) 指定库名
通常动态库静态库名字的格式都是 libxxx.so 或 libxxx.a ,所以这个参数的使用方法是直接加库名 -lxxx ,具体使用方法将在我Linux专栏的另一篇文章《自己动手做动态库与静态库》中详细介绍。
(5)-L 包含的库路径
指定动态库和静态库的路径,后面直接加路径即可。具体使用方法将在我Linux专栏的另一篇文章《自己动手做动态库与静态库》中详细介绍。
(6)-g 生成调试信息
这个选项用于gdb调试的时候,只有在编译的时候加 -g 选项,才能进行gdb调试。
可以看到,加了 -g 选项后,文件变大了,这是因为里面包含了调试所用的信息,关于 -g 选项的更多知识和 gdb 调试相关讲解,将在Linux专栏的另一篇文章《GDB调试器》中详细介绍。
(7)-Wall 显示更多警告信息
当GCC在编译过程中检查出错误的话,它就会中止编译,并报错。但是当检测到警告时却能继续编译并生成可执行文件,这时因为警告只是针对程序结构的诊断信息,它不能说明程序一定有错误,而是说明程序存在风险,或者可能存在错误。GCC提供了非常丰富的警告,但是如果你不启用这些警告的话,GCC编译器是不会报告检测到的警告信息的。
举个例子,我们写一个int类型的main函数,并且不加return语句
我们直接编译程序,可以看到,编译通过,没人任何报错也没有任何警告,并且程序可以运行并打印出值。实际上,main函数没有return语句至少应该提示警告信息的,甚至在VS中,这个文件直接就无法通过编译且直接报错的。
我们再加上 -Wall 选项,可以看到,虽然生成了可执行文件,但是有警告信息提示。
实际上,即使加了 -Wall 选项,也并非所以警告都会提示,有一些警告是不会提示的,比如隐式类型转换等。我们对下面程序编译,程序中有一个int到char的隐式类型转换
编译一下,虽然没有警告,但是程序没有打印任何东西。
(8)-D 指定宏
我们将前面准备好的hello.c进行一点修改,把宏定义删除
编译运行
因为没有宏定义 PRINT ,所以 printf 语句不会执行,也就不会打印任何东西。我们可以通过 -D 来指导一个宏。
gcc hello.c -D PRINT
可以看到, printf 函数执行了。
(9)-lstdc++ 编译C++源文件
直接用gcc编译C++源文件,是无法编译的
编译C++源文件有两种方法,一种是使用 -lstdc++ 选项,另一种是使用 g++ 编译。
总结
通过这篇文章你是不是对程序的编译过程和GCC编译工具链有了更加深刻的认识呢,其实GCC也没什么神秘的吧,哈哈哈哈。有句诗我非常喜欢“纸上得来终觉浅,觉知此事要躬行”,对于GCC的学习绝不能止步于这篇文章,一定要打开自己的虚拟机或者双系统进入你的Linux,一个命令一个命令的敲,一个文件一个文件的看,动手实践才能把知识变成自己的。当然,对Linux的学习更不能止步于此,这里分享一个学习Linux的小妙招,重点来了哦,那就是一定要关注我的Linux专栏,把里面的每一篇文章都看透,嘻嘻嘻~