GCC学习笔记
简介
GCC是GNU Compiler Collection的缩写,它是一套编程语言编译器的集合,在最初的时候只能编译C语言,之后扩展便可以处理C++, Fortran,Objective-C, JAVA和GO等语言。
本文主要介绍GCC编译C项目的过程。
编译过程
对于一个源代码程序.c,要想其被计算机执行,需要大概如下几步:
- 预处理过程:替换头文件、宏定义等,其本质是一个文本替换过程,得到的文件以.i结尾。
- 编译:将文件编译为汇编代码,文件以.s结尾。
- 汇编:使用汇编器将汇编代码转为二进制文件.o。
- 链接:由于.o文件调用了一些动态库或者静态库,需要将这些库代码和.o文件打包(动态库不需要把代码打包)才能得到最终的可运行的二进制文件。
GCC编译参数
编译选项 | 说明 |
---|---|
-E | 预处理源文件,如(gcc -E main.c -o main.i) |
-S | 编译源文件,生成汇编代码,但是不进行汇编(gcc -S main.c -o main.s) |
-c | 编译、汇编源文件,但不链接 |
-o [file1] [file2]/ [file2] -o [file1] | 将file2编译成可执行文件file1 |
-I dir | 指定include文件的目录 |
-g | 生成调试信息 |
-D | 编译程序时指定一个宏, 如-D DEBUG |
-w | 不生成任何警告信息 |
-Wall | 生成所有的警告信息 |
-O0,-O1,O2,O3 | 编译优化,O0无优化,O1缺省值,O3优化级别最高。 |
-l | 程序编译的时候指定使用的库,如-lm是链接math库 |
-fpic/-fPIC | 生成和位置无关的代码,用来制作动态库 |
-shared | 生成共享目标文件,用来生成动态库 |
-std | 指定编译版本,如-std=c99,-std=c++11… |
静态库
对于若干.c程序,我们首先使用编译它们生成各自的.o文件,然后使用ar工具,执行ar rcs libxxx.a(xxx是库名) *.o将他们打包为静态库。再linux下静态库是以.a文件结尾。
demo
1 | . |
现在我们有这样一个简单的项目,head.h声明了五个函数,这五个函数分别由src中的代码实现,在main中我们调用了这几个函数。
1 |
|
如果我们直接编译
1 | gcc main.c -o main -I ./include/ |
则会提示:
1 | /usr/bin/ld: /tmp/ccRyDshG.o: in function `main': |
其实就是因为我们虽然使用了head头文件,但是生成的二进制文件找不到对应的函数实现的二进制文件。因此我们需要把这些函数实现打包成静态库。
进入src目录执行
1 | gcc -c *.c -I ../include/ |
这样我们就对每个c文件生成了对应的二进制文件。
继续执行
1 | ar rcs libcalc.a *.o |
我们就可以把*.o文件打包为libcalc.a静态库文件,库名字为calc,将该文件拷贝到lib目录下。
返回上级目录执行
1 | gcc main.c -o main -I ./include/ -lcalc -L ./lib/ |
运行main即可得到正确结果。
动态库
显然,静态库存在一些缺点,首先是浪费空间,对于每个可执行文件都需要将库打包,不可共用,其次是如果更新了静态库,则还需要重新链接。
动态库则是为解决这两个问题而产生的。动态链接是在程序运行时,如果需要某个库,再将其链接到可执行程序上。Linux中,动态库的文件后缀为.so。
接下来展示如何制作动态库并链接。
同静态库样例,还是一样的程序,只不过在src下,我们执行
1 | gcc -c *.c -fpic -I ../include/ |
用来生成和位置无关的代码(指运行和放置地址无关的代码)
然后执行
1 | gcc -shared *.o -o libcalcd.so |
将*.o打包成calcd动态库。
最后编译main.c
1 | gcc main.c -o main -I ./include/ -l calcd -L ./lib/ |
如果直接运行会报错,这是因为我们需要把动态库的路径保存到环境变量。
在etc的bashrc文件最后一行导入(LD_LIBRARY_PATH是linux下的一个环境变量):
1 | export LD_LIBRARY_PATH=$LD_LIBRARY_PATH: libpath |
使用ldd命令查看动态库依赖情况:
1 | linux-vdso.so.1 (0x00007fffc48d5000) |
运行main即可得到正确输出。