读了一本介绍GCC的书《An Introduction to GCC》,这里记录一下关于GCC的功能和用法。
1. 概念
GCC(GNU C Compiler)早期是专为C语言而开发的编译器自由软件,后来加入了C++语言的支持,再后来增加了大量其他语言的支持,包括Fortran,Object-C,Java等,因此GCC这个词的涵义升级为GNU Compiler Collection。
GCC诞生于1987年,今天已有30岁高龄。
2. gcc.exe的用法
gcc.exe是GCC套件中的C语言编译器,它能够编译、调试和链接源代码并输出可执行文件。
2.1 基础用法
最简单的用法为:
gcc main.c
在命令行提示符窗口中输入这条命令,将编译main.c代码并生成一个可执行文件a.exe。
这里假设main.c中的内容是:
#include <stdio.h> int main(void) { printf("Hello World!\n"); return 0; }
那么在命令行中输入a.exe即可运行并打印出“Hello World!”字样。
2.2 显示警告
-Wall(Warning all)参数可以显示出编译过程中遇到的警告信息。
将上述编译命令改成
gcc -Wall main.c
此时如果源代码中有不合理的代码,则会在编译过程显示警告信息。这些警告信息包括:嵌套块注释,printf等函数的格式参数不匹配,未被使用的变量,隐式声明,返回值不匹配。
推荐所有的编译过程都使用-Wall参数。
2.3 指定输出文件
默认的输出文件名为a.exe,使用-o(output)指令可以输出指定输出文件的文件名:
gcc -Wall main.c -o main.exe
这样就能够生成main.exe而不是a.exe
2.4 编译多文件项目
假如项目中存在多个源文件,比如main.c中调用了hello.c的头文件hello.h,使用了它的函数SayHello()。
那么在命令行中执行:
gcc -Wall main.c hello.c -o main.exe
就能够正确的编译这个项目。
2.5 分步编译
-c(compile)参数用于将源代码文件编译成.o对象文件,然后再将所有的对象文件链接成可执行文件。
gcc -Wall -c main.c hello.c
输出main.o和hello.o两个对象文件。对象文件是二进制文件,不能直接使用文本编辑器读取。
命令行中main.c和hello.c的先后顺序不可颠倒。
利用这些对象文件,可以生成最终的可执行文件:
gcc main.o hello.o -o main.exe
2.6 Makefile
当项目中文件比较多,而这时更改了hello.c,希望只重新编译该文件,而不是重新编译整个项目,使用2.5中的命令行将会异常繁琐。
Makefile使用脚本的方式,将2.5中的编译过程列举出来并依次执行。
2.5中的两行命令可以用Makefile表达:
CC=gcc CFLAGS=-Wall main:main.o hello.o clean: del /f main.exe main.o hello.o
第一行CC(C Compiler)表示C编译器类型,第二行CFLAGS表示参数开关,第三行表示输出文件是main.exe,依赖文件为main.o和hello.o,这两个文件会自动从对应的.c文件中编译生成。最后的clean是一个make.exe的命令函数。注意,最后一行del前需要是一个Tab,而不是空格。
将文件保存在当前目录下,文件名为Makefile,没有扩展名,在命令行中执行:
make
即运行make命令,不带任何参数,将会自动读取当前目录下的Makefile并执行内容。执行完毕将生成main.exe文件。
还可以执行Makefile中设定的clean函数:
make clean
则自动执行删除main.exe, main.o和hello.o文件的操作。
现代的IDE都会使用Makefile来编译项目文件。
2.7 链接外部库
假如使用的是MinGW版本的GCC,自带的库文件通常放在:\MinGW32\lib或\MinGW32\lib32目录下。
观察其中的库名称,会发现所有的库文件名都以lib作为前缀,扩展名为.a。
假如源代码中使用了math.h库,这个数学库需要调用标准库libm.a文件,则需要在命令行中指明:
gcc -Wall main.c -lm -o main.exe
这里的-lm要拆开理解,-l(Library)表示链接外部库,m表示文件libm.a去除了lib前缀和.a扩展名的剩余部分。假如库名字为libABC.a,调用时候就是-lABC。
如果不显式调用库文件,则会造成编译失败。
同时,要注意-lm参数是放在main.c的后面,表示与该文件的调用关系,顺序不能颠倒,位置不能出错。
2.8 库和头文件路径
假如库或者头文件放在其他目录,有两种办法可以通知编译器:
- 将那些目录加入到环境变量
- 中命令行中指定那些目录
命令行中指定头文件目录参数是-I(Include_Path),库目录参数是-L(Library_Path),注意都是大写。
假如main.c调用的头文件中父目录的include目录中,库文件在父目录的lib目录中,那么命令行可以写成:
gcc -Wall -I/../include -L/../lib -lm main.c
2.9 静态库和动态链接库
静态库的后缀是.a,编译时会将其完整的复制到最终的可执行文件中,即静态库最终会成为可执行文件的一部分。
动态链接库后缀是.so(这里仅考虑GNU编译平台,不考虑Windows的DLL),仅在执行前将库文件中需要的那部分代码加载到系统内存中,执行结束则释放,是一个动态的链接过程。这部分内存代码,可以共享给多个不同程序使用,所以也叫共享库。
动态链接库可以让程序体积减小,如果更新库本身,只要接口维持不变,则不会影响应用程序。
如果平台针对一个功能同时提供静态库和动态链接库,那么编译器会优先使用动态链接库。
动态链接库的使用方法与静态链接库相同。(此处存疑,因为书中介绍不甚清楚。)
2.10 指定标准
C语言有C89,C99,有ANSI纯净版和GNU扩展版,各个版本略有不同。
-std(standard)参数可以指定C语言标准版本:
gcc -Wall -std=gnu99 main.c
2.11 宏开关
假设代码中使用了宏开关:
#ifdef ON {//xxxxxx} #end
-D 参数可以可以指定这个ON宏:
gcc -Wall -DON main.c
这等同于在代码中使用:
#define ON
2.12 宏参数
假设代码中使用了宏参数:
#if OPEN==1 {//xxxxxx} #endif
仍然在命令行中使用-D参数:
gcc -Wall -DOPEN=1 main.c
这等同于执行代码:
#define OPEN (1)
如果希望OPEN等于空,那么可以使用双引号进行包裹空字符:
-DOPEN=""
3. gdb.exe的用法
gdb是C语言调试工具。在生成了main.exe之后,执行:
gdb main.exe
这样就打开了gdb的子环境。在这个子环境中,q表示退出,在任何时候输入q字母并回车,就退出gdb环境。
- 在gdb中输入file main,表示读取main.exe程序中的符号列表。
- 运行run命令,将开始执行main.exe。
- 假如main中有一些全局变量,则可以使用print xxx来打印出来。
- 使用break 函数名来设置断点,然后step表示单步执行。
跟常用的IDE上的调试功能一致。
gdb的使用值得使用一本书来介绍,这里点到为止。
4. 代码优化
4.1 基本手段
代码层面的优化包括:
- 表达式消除
- 函数内联化
- 循环展开
- 指令调度
比如在代码中用到的加减乘除运算,在编译器会直接得到结果再进行编译,这样就减少了可执行程序的运算步骤。
一个”小“函数,如果频繁的被调用,则将其内联到具体的调用者体内。
所谓循环展开是指将某些赋值循环展开成扁平的赋值操作。因为一个循环,必然存在检测退出机制,而大多检测操作是无用的操作,所以展开后能够减少指令数。
指令调度则是重新排列指令顺序,以加快执行速度。
4.2 优化选项
上述的一些优化措施,已经集成到了gcc内部,以-o(Optimize)参数形式提供:
- -o0
- -o1
- -o2
- -o3
- -os
-o0表示没有任何优化,等同于不使用该参数。适合调试时候使用。
-o1清除代码层面上的冗余,未做深入优化。这个是最常用的优化级别。
-o2使用了指令调度优化,它没有增加最终文件的大小,却提高了速度,所以适合做程序发布时候的优化项。
-o3使用了较多技术,增加了最终程序的大小,带来了速度上的提升。这种优化项有时候可能不稳定。
-os专为为减小最终程序大小而做的优化项。
书中举了一个例子(链接),在函数中做大量、复杂的数学运算。如果不做优化,程序执行时间为13秒,不同的优化选项能带来不同的提升,-o3选项下,程序执行时间减小到5.4秒,改善比较可观。
5. 汇编文件
使用cpp.exe可以将源代码文件进行预处理,生成.i文件。
cpp main.c > main.i
这个main.i文件可以进一步转成汇编文件:
gcc -Wall -S main.i
在当前目录下将生成main.s文件,使用编辑器打开可以看到汇编代码。
进一步可以使用as.exe将汇编文件打包成对象文件:
as main.s -o main.o
然后使用链接器ld.exe生成可执行文件,但是ld命令的参数比较复杂,通常直接利用gcc把这些事情给做了。
6. 其他功能
6.1 输出符号列表
nm.exe可以输出可执行文件的符号表:
nm main.exe > main.map
输出的map文件包含了各个符号的存储地址和类型。
6.2 执行时间
gprof.exe可以记录各个函数的调用次数、执行时间等。
gprof main.exe
这个main.exe需要预先使用-pg指令进行处理:
gcc -Wall -pg main.c -o main.exe
可以参考这个示例(链接)进行操作。
6.3 代码执行次数
gcov用于生成测试报告,记录源代码中每一行被执行的次数,如果代码行未被执行,也会被统计出来,所以叫做覆盖性测试。
先生成特殊处理的可执行文件:
gcc -Wall -fprofile-arcs -ftest-coverage main.c
执行main.exe,生成必要的中间文件:
main.exe
然后再使用gcov.exe读取main.c:
gcov main.c
此时观察源代码所在目录,会有一个main.c.gcov的文件,打开它就能够看到各个代码行被执行了多少次。
7. 小结
至此,对gcc的各功能有了概念性的了解,以后再遇到编译器选项、符号表、优化等问题,可以再做深入探究。
这本书是免费书,阅读地址:http://www.network-theory.co.uk/docs/gccintro/index.html
比较喜欢这种100页的书,一两天就读完了,好开心。
(完)
博客都是干货,,很喜欢