gcc编译工具使用记录
前言
其实现在基本上不直接使用gcc/g++来编译了,因为工程中涉及的包、文件很多,用cmake比较方便。
但是记录一下gcc的使用,对于编译过程的理解还是有好处的。
注意:在使用上gcc和g++区别不大,g++在链接时会自动链接STL库但gcc不会。
gcc编译过程
如下图所见,包含预编译、编译、汇编和链接四个阶段。
预编译:把.h文件插入源文件中(#include),替换宏定义(#define),选择需要编译的代码(#ifdef等),删除注释等,产生预编译文件(.i文件)
编译:检查C代码语法错误,然后把C代码翻译为汇编代码(.s文件)。
汇编:将汇编代码翻译为机器代码(二进制.o文件)。
链接:将以上的二进制码文件与需要用到的各种库文件连接,生成可执行文件。
代码文件示例
新建一个文件夹example,在其中建立src、include、lib、bin和build五个文件夹:
mkdir example && cd example
mkdir src include lib bin build
然后在src中创建main.c如下:
#include "stdio.h"
#include "func.h"
int main(){
int a=1, b=6;
int c = func(a, b);
printf("%d + %d = %d\n", a, b, c);
return 1;
}
在lib中创建func.c如下:
int func(int a, int b){
return a+b;
}
直接编译出可执行文件
在gcc中,直接使用gcc即可将源码编译为可执行文件,并自动命名为a.out:
# terminal在example/build目录下
gcc ../src/main.c ../lib/func.c
./a.out
可以通过-o参数设置编译产生文件的名字:
gcc -o main ../src/main.c ../lib/func.c
./main
分步编译
除了直接编译出可执行文件外,也可以通过参数设置,一步步的完成整个源码的编译。
预编译生成.i预编译文件
参数-E表示预编译后停止,与参数-o一同使用来保存预编译文件。
gcc -E -o main.i ../src/main.c
gcc -E -o func.i ../lib/func.c
ls
# func.i main.i
编译生成.s文件
参数-S表示编译后停止
gcc -S func.i main.i
ls
# func.s main.s func.i main.i
汇编生成.o文件
参数-c表示汇编后停止
gcc -c main.s
gcc -c func.s
ls
# func.o main.o func.s main.s func.i main.i
链接生成可执行文件
gcc func.o main.o -o main
ls
# a.out func.o main.o func.s main.s func.i main.i
./a.out
# 1 + 6 = 7
链接库
当源文件较多时不易于管理,编译多文件也会花费更多的时间,因此可以通过gcc将一些功能函数打包成库文件的形式,方便调用。
下面将把func.c打包成库文件。
动态编译
使用动态库(.so或.dll)进行链接。链接动态库时,基本不会把动态库中需要的代码拷贝到可执行文件中,仅在运行时执行相关函数时才进行真正的链接。
动态编译产生的可执行文件比较小。
生成动态链接库.so
通过-shared生成动态库,最好同时加上-fPIC命令:
gcc -shared -fPIC ../lib/func.c -o ../lib/func.so
静态编译
使用静态库(.a或.lib)进行链接。链接静态库时,编译器会把目标文件和静态库中目标文件需要的代码一同拷贝进可执行文件中。
静态编译产生的可执行文件较大,并且如果静态库发生了变化,就需要重新编译整个可执行文件。
生成静态链接库.a
通过ar生成静态库,需要从.o文件生成:
ar crsv ../lib/libfunc.a func.o
编译时指定链接库
无论静态编译还是动态编译都需要指定链接库,最直白的方法是直接通过路径链接,库的路径必须在.o文件的后面:
gcc main.o ../lib/libfunc.a
# or
gcc main.o ../lib/libfunc.so
也可以通过-l指定库的名字和-L指定编译时搜索库的路径,此时如果路径同时存在同名动态和静态库,会优先选择动态链接,可以使用-static强制静态链接:
# 动态链接
gcc main.o -lfunc -L ../lib
# 静态链接
gcc main.o -lfunc -L ../lib -static
运行时指定链接库
静态编译时,由于已经从链接库中把代码拷贝到可执行文件中,因此可以直接运行。
动态编译时,编译器只是将动态库符号位置告诉了可执行文件,在运行时仍然需要动态库来实现链接。而当动态编译时使用-L参数只是指定了编译时搜寻库的位置。
因此需要通过-rpath来指定运行时搜索库的位置:
gcc gcc main.o -lfunc -L ../lib -Wl,-rpath=../lib
指定包含文件
大型工程往往包含很多第三方头文件.h,这些文件的位置可以通过-I指定位置:
gcc -i ../include ../src/main.c
将.h文件对应的.c文件编译成库,然后将.h文件与库文件发布,可以较好的保护.c文件的内容。