库 是一种代码的二进制的封装形式,将.o文件打包封装就成了库。库可以在任何地方使用,但用户却不能看见他的具体实现。库有利于代码模块化,只要接口设计得合理,改变库的内部实现,不会影响到用户级别的代码使用。
动态库
1.封装动态库 假设有源代码sum.c, sub.c
gcc sum.c -c -o sum.o
gcc sub.c -c -o sub.o
//封装成库
gcc -shared -fPIC -o libmymath.so sum.o sub.o
/*
-shared 表示要编译一个共享库
-fPIC 表示要生成与位置无关的代码
-o 要创建的库的名称,一般约定库的名称格式如下:
lib库名.so
*/
2.动态库的编译
只需把.h与.so文件提供给用户使用即可
用户编译形式如下:
gcc main.c -I 头文件路径 -L 库文件路径 -l库名
ex:
gcc test.c -I ../mymath/ -L ../mymath/ -lmymath
//路径可以是相对路径也可以是绝对路径
3.依赖动态库的程序执行
用户执行时需要指明库所在的路径,通常通过添加环境变量的方式实现
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:库文件路径
查看库环境变量
echo $LD_LIBRARY_PATH
使用命令lld可查看
静态库
1.封装静态库
假设有源代码sum.c, sub.c
gcc sum.c -c -o sum.o
gcc sub.c -c -o sub.o
//静态库的后缀名是.a
ar -rc libmymath.a sum.o sub.o
//-rc 中的r 表示插入目标文件到静态库中,c表示创建指定的静态库
2.静态库编译
gcc main.c -I 头文件路径 -L 库文件路径 -l库名
ex:
gcc test.c -I ../mymath/ -L ../mymath/ -lmymath
//如果该路径下有动态库,则会优先使用动态库
//如果要静态链接自己或第三方的库,但又要动态链接系统的库,那么编译方式如下
gcc xxx.c -I 头文件路径 静态库的完整的名称
ex:
gcc xxx.c -I ../mymath/ ../mymath/libmymath.a
//全部使用静态库,在编译时加 -static
gcc main.c -I 头文件路径 -L 库文件路径 -l库名 -static
动态库与静态库的主要区别
静态库和动态库都是二进制文件(目标文件)的封装
对于动态库,编译程序时,并没有把动态库的内容复制到可执行文件中去,仅仅是做了一个标记,表示可执行文件需要用到某个动态库,当执行程序时,首先需要去LD_LIBRARY_PATH指定的路径下(或库的标准路径,如/lib,如/usr/lib等)查找需要的动态库,才能正常运行。
对于静态库,编译程序时,会把静态库中的内容复制到可执行文件中去,运行程序时,就不再需要那个静态库了。
我们使用得最多的是动态库,理由: 1、程序运行时,动态库在内存中只需要一份,而静态库,则可能会有多份拷贝,造成所谓的代码 冗余。 2、当库更新升级时,对于动态库来说只要接口不变,则不需要重新编译用户程序,如果是静态库,则库改变了,所有使用该库的程序都必须重新编译。
C&C++混合编程中库的处理
c++兼容c,能够直接使用这些功能,用c开发的功能如果打包成库了(经c编译器编译成目标文件),目标文件中的函数名就已经确定下来。而c++编译器会对函数名进行处理,会由于函数名字不匹配而导致调用c库中的函数失败
解决办法:
C++提供了一个关键字: extern “C”, 称为链接指示,通常写在头文件中。但c编译器并不认识这个关键字。因此用法如下:
#ifdef __cplusplus
extern "C"
{
#endif
int sum(int a, int b); // C函数声明
int sub(int a, int b); // 可包含多个函数声明
#ifdef __cplusplus
}
#endif
负责任的程序员,理应在c语言编写的库中加入该关键字