大家好,我是阿木实验室的松溪,今天给大家介绍一下,linux系统中,库的概念。
一、库的简介
什么是库,库简单地说,就是模块。用于提供不同功能的模块,比如我们经常会用的ceres库,eigen库,pcl库等等。库从本质上来说,是一种可以执行代码的二进制形式,可以被载入到内存中使用,在Liunx系统中,库以文件的形式存在,并且可以分为动态链接库和静态链接库两种,简称为动态库和静态库。其中,静态库文件的后缀为.a,动态库文件的后缀为.so。无论是动态库还是静态库,它们无非是向调用的人提供变量、函数或者类。
二、库的区别
静态库和动态库的区别在于:二者代码被载入的时刻不同。静态库在程序编译的时候会被链接到目标代码中,目标程序运行的时候将不再需要该库,移植方便,但是体积会变大,浪费空间和资源,因为与之相关的所有文件都会链接合成一个可执行文件,导致可执行文件的体积变大。动态库在程序编译的时候并不会被链接到目标代码中,而是在程序运行的时候才被载入,因此可执行文件体积较小。
基本上,大部分的库都是动态库,比如:eigen,ceres等,这就是我们通常所说的依赖项,如果你的程序中有需要某个库,而你的环境中没有这样的库,那么在编译的时候就会出现这样的错误:xxxx.h文件未找到。这就是典型的缺少动态库的bug。对此,只需要安装对应的动态库即可解决。这个问题经常出现在当你clone某一个代码下来,但是却没有这个代码的运行环境,当你编译的时候,就会出现上面的报错。是不是又get到了一个小技巧呢?
三、静态库的创建和使用
在Linux系统中创建静态库的过程如下:
1. 编辑源文件(.c或.cpp文件)
2. 通过 gcc -c xxx.c 或 g++ -c xxx.cpp 生成目标文件(.o文件)
3. 使用 ar 命令归档目标文件,生成静态库
4. 配合静态库写一个头文件,文件里的内容就是提供给调用者使用的函数、变量或者类的声明。
在实际创建之前,有必要了解一下,ar命令的使用。ar命令不但可以创建静态库,也可以修改或提取已有静态库中的信息,其基本用法如下:
ar [option] libxxx.a xx1.o xx2.o xx3.o
其中,libxxx.a是生成静态库的文件名字,xxx是自己设定的名称,lib表示该文件是一个库,所有在Linux下的库,都遵守这种命名。即libxxx.a或者libxxx.so。xx1.o xx2.o为静态库的目标代码文件,可以有多个。option常见的选项如下:
-c:创建一个库,无论库是否存在,都会创建
-s:创建目标文件索引
-r:在库中插入模块,如插入的模块名已经在库中存在,则将会替换。如果有一个模块不存在,将会保存,并不会替换其他同名模块
-t:显示库文件中有哪些目标文件。仅仅显示
-tv:显示库文件有哪些目标文件。包括文件名、时间、大小等
-s:显示静态库文件中的索引表
下面将讲解如何创建和使用一个静态库
新建一个源文件,test.cpp,复制以下代码:
#include <IOStream>
void show_age(int age) {
std::cout << "Your age is: " << age << std::endl;
}
然后输入命令
g++ -c test.cpp
ar rcs libshowage.a test.o
这样就生成了一个静态库,要想使用,需要编写另一个源文件,demo.cpp。复制以下代码
#include <iostream>
extern void show_age(int age); //声明要使用的函数
int main(int argc, char** argv) {
show_age(18);
std::cout << "Hi" << std::endl;
}
CMakeLists.txt文件
cmake_minimum_required(VERSION 3.0)
project(demo)
add_executable(demo demo.cpp)
target_link_libraries(demo -L.. -lshowage)
其中,-L参数表示从什么地方找这个库,(..)表示上一级目录。-l指定具体的库,其中lib和.a不需要显示写出,编译器会自动去寻找libshowage.a这个文件。这也就是为什么库命名的时候要以lib开头。
四、动态库的创建和使用
在Linux系统中,存放动态库的路径一般为/usr/lib。在Linux系统下进行的链接,默认是先链接动态库,如果同时存在静态库和动态库,如果不特别指出,将与动态库链接。这样有助于节省空间。
同理,我们创建一个动态库,test.cpp。代码同上,不做改变。只需要修改生成动态库的命令。该命令如下:
g++ test.cpp -fPIC -shared -o libshowage.so
-shared 表示生成共享库
-fPIC表明使用地址无关代码。PIC全称是Position Independent Code。在Linux系统下编译共享库时,必须加上这个参数,否则在链接的时候将会报错。因为共享库文件可能会被不同的进程加载到不同的位置上,如果共享对象中的指令使用了绝对地址、外部模块地址等,那么该库在被加载的时候,就需要修改地址,这样就不能实现多进程共享一份物理内存。
编写一个调用该库的函数,demo.cpp。内容还是不变。CMakeLists.txt也不变。
五、多个文件生成一个动态库
创建test1.cpp文件,复制以下代码
#include <iostream>
void show_age_1(int age) {
std::cout << "This is lib_1: " << age << std::endl;
}
创建test2.cpp文件,复制以下代码
#include <iostream>
void show_age_2(int age) {
std::cout << "This is lib_2: " << age << std::endl;
}
创建动态库
g++ test1.cpp test2.cpp -fPIC -shared -o libshowage.so
需要说明的是,多个文件创建动态库的时候,不能有同名的函数、类、变量的声明,否则将无法创建,因为就算创建成功以后,当调用的时候,编译器并不知道该去调用哪一个源文件里面的声明。
创建 main.cpp 文件,使用这个库,复制以下代码
#include <iostream>
//声明要使用的函数
extern void show_age_1(int age);
extern void show_age_2(int age);
int main(int argc, char** argv) {
show_age_1(18);
show_age_2(36);
}
CMakeLists.txt文件如下
cmake_minimum_required(VERSION 3.0)
project(demo)
add_executable(main main.cpp)
target_link_libraries(main -L.. -lshowage)
当我们运行的时候,会发现这样的错误
./main: error while loading shared libraries: libshowage.so: cannot open shared object file: No such file or directory
在编译的时候,我们告诉了编译器,在什么地方去寻找该库,但是在运行的时候,由于该库是动态库,其代码没有被链接到目标代码中,因此,在运行的时候,会提示找不到该库。
因此,只需要将动态库放到默认的路径上或者告诉其指定路径即可。
在Linux系统中,一般将库放在以下三个地方。其中,/lib用于放置系统级别(或者说内核级别)的库文件,/usr/lib 用于放置程序级别的库文件,/usr/local/lib 为用户级别,一般用户编译的都放在这里,以及源码编译安装,例如:opencv的源码编译。
因此,我们要将自己的库放在 /usr/local/lib 下,输入以下命令
sudo cp ~/C++/demo/libshowage.so /usr/local/lib
当我们移动以后,需要输入以下命令(必须),更新
sudo ldconfig
这样,当我们再次运行的时候,就不会报错了。切记,每次有动态库在上述目录中发生变动,都需要输入这个命令更新,否则还是无法识别。
六、通过apt安装缺少的动态库
当我们从github上clone代码的时候,在编译的时候,经常出现xxx.h文件不存在,或者No such file or directory。其本质上就是你的环境和别人的环境不一致,缺少相应的库。一般的解决方式是,将该缺少的头文件复制到百度,查一查这个头文件是来自哪一个库。然后打开终端,输入命令
sudo apt install libxxx-dev
即可
如果是自定义的动态库,一般在readme文档中有说明。
同理,当你向开源社区贡献开源代码的同时,也请务必告知运行的环境以及所需要的库。
七、总结
相信通过本文的介绍,读者对库有了一个简单的认识,在以后的工作和学习过程中,遇到类似的问题,也有了解决问题的思路和方法。
阿木实验室致力于为机器人研发提供开源软硬件工具和课程服务,让研发更高效!
- End -
技术发展的日新月异,阿木实验室将紧跟技术的脚步,不断把机器人行业最新的技术和硬件推荐给大家。看到经过我们培训的学员在技术上突飞猛进,是我们培训最大的价值。如果你在机器人行业,就请关注我们的公众号,我们将持续发布机器人行业最有价值的信息和技术。