GCC学习笔记

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
2
3
4
5
6
7
8
9
10
11
.
├── include
│   └── head.h
├── lib
├── main.c
└── src
├── add.c
├── div.c
├── mod.c
├── mul.c
└── sub.c

现在我们有这样一个简单的项目,head.h声明了五个函数,这五个函数分别由src中的代码实现,在main中我们调用了这几个函数。

1
2
3
4
5
6
7
8
9
#include<stdio.h>
#include "head.h"


int main(){
printf("%d\n", add(1, 2));
printf("%d\n", sub(1, 2));
...
}

如果我们直接编译

1
gcc main.c -o main -I ./include/

则会提示:

1
2
3
4
/usr/bin/ld: /tmp/ccRyDshG.o: in function `main':
main.c:(.text+0x13): undefined reference to `add'
/usr/bin/ld: main.c:(.text+0x35): undefined reference to `sub'
collect2: error: ld returned 1 exit status

其实就是因为我们虽然使用了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
2
3
4
linux-vdso.so.1 (0x00007fffc48d5000)
libcalcd.so => /mnt/d/cpplearning/2.0/gcc/dynamic_lib/lib/libcalcd.so (0x00007f554b830000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f554b630000)
/lib64/ld-linux-x86-64.so.2 (0x00007f554b84b000)

运行main即可得到正确输出。