一、gcc编译
编译过程主要分为四个阶段:
- 预处理:源文件.c → 预加载.i
- 编译:预加载.i → 汇编代码.s
- 汇编:汇编代码.s → 目标代码.o
- 链接:目标代码.o → 可执行程序\库文件
1、编译过程
1.1、预处理(Pre-Processing)
主要处理源文件中的#indef、#include和#define预处理命令,将一些include的头文件和一些宏定义,放到源文件中
gcc -E test.c -o test.i
-E:预处理;test.c:源文件;test.i:将结果生成的文件;
总结:通过对源文件test.c使用E选项来生成中间文件test.i
1.2、编译(Compoling)
将预处理后的代码转换为汇编代码
gcc -S test.i -o test.s
-S:编译;test.s:汇编代码文件
1.3、汇编(Assembling)
通过汇编器将汇编代码转为目标代码
gcc -c test.s -o test.o
-c:汇编;test .o:二进制目标文件;
1.4、链接(Linking)
合并启动代码、库文件及其它目标文件
输出文件:可执行程序(windows是.exe;linux是.out)
gcc test.o example.o -o test
2、完整流程
gcc test.c -o test
上面的步骤可以一步到位; 不加-o选项时生成a.out可执行文件
gcc编译选项 | 说明 |
---|---|
-E | 预处理指定的源文件,不进行编译 |
-S | 编译指定的源文件,但是不进行汇编 |
-c | 编译、汇编指定的源文件,但是不进行链接 |
-o | 将文件file2编译 成可执行文件file1 |
-I directory | 指定include包含文件的搜索目录 |
-g | 在编译的时候,生成调试信息,该程序可以被调试器调试 |
-D | 在程序编译的时候,指定一个宏 |
-w | 不生成任何警告信息 |
运行程序
./程序名
3、gcc和g++
gcc和g++两者都可以编译的
后缀为.c的,gcc把它当做c程序,g++当做c++程序
后缀为.cpp的,两者都会认为是C++程序,c++的语法规则更加严谨一些
编译阶段,g++会调用gcc,对于c++代码,两者是等价的,但是因为gcc命令不能自动和c++程序使用的库链接,所以通常用g++来完成链接,为了统一,干脆就都用g++了。
二、静态库和动态库
1、介绍
- 静态链接即在编译链接时,将代码所使用到的静态库文件代码全部加入到可执行文件中(拷贝实质上是将库文件的代码展开拷贝至我们的可执行程序的代码段。),这样做的缺点是可执行文件会生成的比较大,优点是此时再运行可执行文件就不需要再查找库了。静态库文件一般以 .a 结尾。
- 动态库链接不会在编译时将动态库文件的代码加入到可执行文件中,而是在可执行文件运行时,去查找所需的动态库,并将其加载到相应的进程中,并且不同的进程可以共享这些动态库,动态库文件一般以 .so 结尾。
linux库文件的命名通常以lib为前缀,后面是库名称和文件扩展名。比如math的静态库文件可能是libmath.a;对应的动态库文件可能是libmath.so。
2、静态库
命名规则
Linux: libxxx.a
lib:前缀(固定)
xxx: 库的名字
.a :后缀(固定)
windows : libxxx.lib
静态库的制作
- 在一个头文件.h文件中声明静态库所导出的函数
- 在一个源文件.c文件中实现静态库所导出的函数
- 编译源文件生成.o文件(没有链接)
- 将可执行代码所在目标文件加入到某个静态库中,并将静态库拷贝到系统默认的存放库目录下(/usr/lib或/lib)
创建静态库的命令:
ar rcs 库名 .o文件 .o文件 ...
示例:
1、头文件mylib.h——声明静态库所导出的函数
#ifndef _mylib_H_
#define _mylib_H_
void welcome();
void outstring (const char * str);
#endif
2、对应于头文件的源文件mylib.c——实现静态库所导出的函数
#include "mylib.h"
#include <stdio.h>
void welcome() {
printf("welcome to mylib\n");
}
void outstring(const char * str) {
if(str != NULL)
printf("%s", str);
}
3、编译mylib.c生成目标文件
gcc -c mylib.c -o mylib.o
4、将目标文件加入到静态库中去,静态库为libmylib.a
ar rcs libmylib.a mylib.o
/* r —在库中加入新成员文件,如果要加入的成员文件存在,则替换之默认情况下,新的成员文件增加在库的结尾处
/* c 创建一个库
/* s 无论ar命令是否修改了库内容,都强制重新生成库符号表
5、将静态库拷贝到linux的库目录(/usr/lib或/lib)下
cp libmylib.a /usr/lib/libmylib.a
如何调用静态库
在.c源文件在编译时做静态链接
测试test.c
#include “mylib.h”
#include <stdio.h>
int main()
{
printf (“create and use library:\n”);
welcome(); //静态库中的函数原型
outstring(“It’s successful\n”); //静态库中的函数原型
}
调用
gcc test.c -o test -l mylib
但是最好是带上头文件和库的地址,防止找不到了
gcc test.c -o test -I ./mylib/include/ -l mylib -L ./lib
我们交付库的时候,是将库文件.a.so+匹配的头文件都交给别人才可以。
3、动态库
动态库的创建
在Linux环境下,只要在编译函数库源程序时加上-fPIC(位置无关代码)、 -shared选项即可生成动态链接库
// gcc得到.o文件,得到和位置无关的代码
gcc -c -fpic/-fPIC a.c b.c
// 加-fpic得到和位置无关的这个必须加,如果要制作动态库
// gcc得到动态库
gcc -shared a.o b.o -o libcalc.so
// -shared共享的
// -o指定生成文件的名称
动态库的使用
gcc main.c -o mymath -I mylib/include/ -l mymath -L mylib/lib
但是这个时候还有可能会报错,为什么?
因为我们指明的是向gcc编译器指明的。在静态链接的时候,编译的时候,我们是直接将库文件拷贝到可执行程序中来的,后面就不需要再寻找库文件了。但是动态链接不一样,它没有将库函数拷贝到可执行程序中,它是在程序运行的时候才慢慢加载到可执行程序中的,在gcc编译完成后,和gcc就没有关系了,所以在程序运行起来的时候,OS和shell也是需要知道库在哪里的,不然加载不进来。这就是为什么上面报错,因为我们的库还没有在系统路径下,OS和shell不知道库在哪里
解决方法
1、修改配置,让动态载入器能够去那个路径下进行查找
pwd先查看当前lib下动态库的路径
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:动态库路径
echo $LD_LIBRARY_PATH
查看修改后的路径
ldd main
查看依赖关系,但是上面是临时的关闭终端后他就取消掉了
2、用户级配置
在home目录下ll,有一个隐藏文件.bashrc,在这个文件下面加上
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:动态库路径
让其生效:./source .bashrc
3、系统级别配置
需要用户权限在profile下
sudo vim /etc/profile
同样在里面插入那一行
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:动态库路径
然后sudo source /etc/profile
4、静态库与动态库比较
静态库:
优点:
- 静态库被打包到应用程序中加载速度快
- 发布程序无需提供静态库,移植方便
缺点:
- 消耗系统资源、浪费内存
- 更新、部署、发布麻烦
动态库:
优点:
- 可以实现进程间资源共享
- 更新、部署、发布简单
- 可以控制何时加载动态库
缺点:
- 加载速度比静态库慢
- 发布程序时需要提供依赖的动态库