win平台与linux平台静态库动态库生成示例
前言
一切皆变量,CPU 访问内存时需要的是地址,而不是变量名和函数名!
变量名和函数名只是地址的一种助记符,当源文件被编译和链接成可执行程序后,它们都会被替换成地址。
编译和链接过程的一项重要任务就是找到这些名称所对应的地址。
- 预处理
调用cpp对源码进行预处理,对其中的包含(include)、预编译语句(宏定义等)进行分析,根据这些预处理指令修改源文件,生成 .i
文件。
C/C++提供的预处理指令主要有:文件包含(include)、宏定义(macro)、条件编译等。
C/C++预处理器只做宏替换
和文本替换
。
C/C++预处理是不会做任何语法检查的,不仅是因为它不具备语法检查功能,也因为预处理命令不属于C/C++语句(这也是定义宏时不要加分号的原因),语法检查是编译器的工作。
通过预处理之后,我们得到的是也仅仅是真正的源代码。
- 编译
调用ccl进行编译,通过词法分析和语法分析,在确认所有的指令都符合语法规则之后,将其翻译成等价的中间代码表示或汇编代码,生成 .s
文件。
- 汇编
调用as进行汇编,把汇编语言代码翻译成目标机器指令,生成.o
文件。
- 链接
将有关的目标文件彼此相连接,也即将在一个文件中引用的符号同该符号在另外一个文件中的定义连接起来,使得所有的这些目标文件成为一个能够被操作系统装入执行的统一整体。
预编译:将.cpp文件转化成.i文件,使用的gcc命令是:gcc –E,对应于预处理命令cpp
编译:将.cpp/.h文件转换成.s文件,使用的gcc命令是:gcc –S,对应于编译命令cc –S
汇编:将.s文件转化成.o文件,使用的gcc 命令是:gcc –c,对应于汇编命令是as
链接:将.o文件转化成可执行程序,使用的gcc 命令是:gcc,对应于链接命令是ld
测试文件内容
SoDemoTest.h
#ifndef __SO_DEMO_TEST_HEADER__
#define __SO_DEMO_TEST_HEADER__
#include <iostream>
using namespace std;
void one();
void two();
void three();
#endif
one.cpp
#include "SoDemoTest.h"
void one(){cout << "call one() function." << endl;}
two.cpp
#include "SoDemoTest.h"
void two(){cout << "call two() function." << endl;}
three.cpp
#include "SoDemoTest.h"
void three(){cout << "call three() function." << endl;}
main.cpp
#include "SoDemoTest.h"
int main(){
one();
two();
three();
return 0;
}
Windows10
- MGW64
MGW64带objdump.exe
工具
# 源文件生成动态库
g++ one.cpp two.cpp three.cpp -fPIC -shared -o libdynamic.dll
# 或者
g++ one.cpp two.cpp three.cpp -shared -o libdynamic.dll
# 链接动态库
g++ main.cpp -LC:\Users\JHL\Desktop\新建文件夹 -ldynamic -o main
.\main.exe # 动态库放在环境变量path任意目录下即可
# 源文件生成静态库
g++ -c one.cpp two.cpp three.cpp # 生成源文件的目标文件
ar rcs static.a one.o two.o three.o # 生成静态库 static.a
# 链接静态库
g++ main.cpp static.a -o main
.\main.exe # 跟static.a已经没关系了
Liunx
# 生成动态库
g++ one.cpp two.cpp three.cpp -fPIC -shared -o libtest.so # 生成动态库 libtest.so
g++ main.cpp -L动态库的查找路径 -ltest -o main # 生成可执行文件
./main
ldd main
mv libtest.so /lib64/libtest.so
./main
# 源文件生成静态库
g++ -c one.cpp two.cpp three.cpp # 生成源文件的目标文件
ar rcs static.a one.o two.o three.o # 生成静态库 static.a
# 链接静态库
g++ main.cpp static.a -o main
.\main.exe # 跟static.a已经没关系了
拓展
Linux下的静态库通常以
.a
结尾,但在 Winodws下为.lib
或者.a
结尾)Linux下的动态库以
.so
或.so.y
结尾,其中y代表版本号(Windows下为.dll
),而且,Linux下的库必须以lib
开头,用于系统识别(如:libjpeg.a
,libsdl.so
等等)
- Linux动态库默认搜索路径
/lib64、/usr/lib64、/lib、/usr/lib
- 系统头文件目录
/usr/include
- 常用命令
ldd main
:查看二进制可执行文件链接的动态链接库信息,例如ldd nginx
g++ -c main.cpp
:以单个xx.cpp源文件为单位只编译出xx.o的二进制文件(称为:目标文件)
g++ xx.o yy.o -o main
:链接所有相关的目标文件连同用到的静态库、动态库、运行时库到最终独立的可执行文件
file main
:查看文件格式信息
readelf -h main
:查看ELF文件的基本信息(ELF文件是Linux系统可执行文件的通用格式,windows系统的可执行文件通用格式为PE,二者非常相似,但是不兼容,都是对二进制代码的一种封装)
readelf -S main
:查看程序的区块,包含机器代码,与程序的数据
objdump -s -d main.o/main
:查看ELF文件中的内容
objdump -r main.o
:查看目标文件的重定位表,用于查看哪些函数需要被重定位,以及被重定位位置的偏移量
1、编译动态库.so文件
将这几个文件编译成动态库libtest.so的命令如下:
g++ one.cpp two.cpp three.cpp -fPIC -shared -o libtest.so
-shared
该选项指定生成动态连接库(让连接器生成T类型的导出符号表,有时候也生成弱连接W类型的导出符号),不用该标志外部程序无法连接。相当于一个可执行文件
-fPIC
:表示编译为位置独立的代码,不用此选项的话编译后的代码是位置相关的所以动态载入时是通过代码拷贝的方式来满足不同进程的需要,而不能达到真正代码段共享的目的。
-L.
:表示要连接的库在当前目录中
-ltest
:编译器查找动态连接库时有隐含的命名规则,即在给出的名字前面加上lib
,后面加上.so
来确定库的名称
LD_LIBRARY_PATH
:这个环境变量指示动态连接器可以装载动态库的路径。
2、链接动态库
将main.cpp
与libtest.so
链接成一个可执行文件main
命令如下:
g++ main.cpp -L. -ltest -o main
测试可执行程序main
是否已经链接动态库libtest.so
,如果列出libtest.so
的链接信息,就说明正常链接。可以执行以下命令:
[root@test ~]# ldd main
linux-vdso.so.1 => (0x00007ffd913fa000)
libtest.so => /lib64/libtest.so (0x00007f3feaa8e000) # 此处即为链接成功
libstdc++.so.6 => /lib64/libstdc++.so.6 (0x00007f3fea786000)
libm.so.6 => /lib64/libm.so.6 (0x00007f3fea484000)
libgcc_s.so.1 => /lib64/libgcc_s.so.1 (0x00007f3fea26e000)
libc.so.6 => /lib64/libc.so.6 (0x00007f3fe9ea0000)
/lib64/ld-linux-x86-64.so.2 (0x00007f3feac90000)
3、注意的问题
调用动态库的时候有几个问题会经常碰到,有时,明明已经将库的头文件所在目录通过 “-I” include
进来了,库所在文件通过 “-L”
参数引导,并指定了“-l”
的库名,
但通过ldd
命令察看时,就是找不到指定链接的.so
动态库文件,这时要作的就是通过修改 LD_LIBRARY_PATH
或者/etc/ld.so.conf
文件来指定动态库的目录。
LD_LIBRARY_PATH: 动态库的查找路径
设置:
方法一: export LD_LIBRARY_PATH=/xx/.so文件存放目录
这种方法只是针对当前这个bash实例生效
方法二: 修改~/.bashrc或~/.bash_profile或系统级别的/etc/profile
vim /etc/profile
export LD_LIBRARY_PATH=/xx/.so文件存放目录
source /etc/profile
方法三:这个没有修改LD_LIBRARY_PATH但是效果是一样的实现动态库的查找,
1、vim /etc/ld.so.conf
在第1行:include ld.so.conf.d/*.conf内容的下方填入.so文件存放的目录全路径
例如:/xx/.so文件存放目录
2、保存过后ldconfig一下(ldconfig 命令的用途,主要是在默认搜寻目录(/lib、/lib64和/usr/lib、/usr/lib64)以及动态库配置文件/etc/ld.so.conf内所列的目录下,搜索出可共享的动态链接库(格式如前介绍,lib*.so*),进而创建出动态装入程序(ld.so)所需的连接和缓存文件.缓存文件默认为/etc/ld.so.cache,此文件保存已排好序的动态链接库名字列表.)
3、一般会将自己编译的动态库与头文件放到/usr/local/include与/usr/local/lib目录中
方法三设置稍微麻烦,好处是比较不受用户的限制。
通常这样就可以解决库无法链接的问题。
原文:https://codeleading.com/article/43326414671/
示例
1.cpp
#include<iostream>
#include<boost/any.hpp>
using namespace std;
int add(int a,int b);
int main(int arg,char** argv){
boost::any a;
a = 1;
a = "123123";
cout << add(1,2) << endl;
cout << &a << endl;
return 0;
}
2.cpp
int add(int a,int b){return a+b;}
编译命令
# 1.编译生成目标文件 1.o 2.o
g++ -c 1.cpp 2.cpp ===>> 1.o 2.o
# 2.链接生成可执行文件
g++ -o main.exe 1.o 2.o ===>>> main.exe
或者把2.cpp编译成动态库在链接
# 编译 1.cpp
g++ -c 1.cpp ===>> 1.o
# 编译 2.cpp
g++ -shared -o 2.dll 2.cpp ===>> 2.dll
# 等价于
g++ -c 2.cpp ===>> 2.o
g++ -shared -o 2.dll 2.o ===>> 2.dll
# 链接成可执行文件
g++ -o main.exe 1.cpp 2.dll ===>> main.exe
1、注意链接时是通过定义的符号,或者方法签名去匹配链接的,头文件不是关键的,关键的是在链接环节中所有1.cpp中使用到的符号都能匹配对应的实现(执行地址),否则就会报错符号未定义
2、g++默认的头文件与库文件搜索目录规则
默认搜索的头文件目录为:g++.exe
文件所在目录的..\include
目录
默认搜索的头文件目录为:g++.exe
文件所在目录的..\lib
目录
注意
// main.cpp
#include<iostream>
using namespace std;
void test();
int main(){
cout << "this is main" << endl;
test();
return 0;
}
// 1.cpp
#include<iostream>
using namespace std;
void test(){cout << "111111111";}
void test1111111(){cout << "test1111111";}
// 2.cpp
#include<iostream>
using namespace std;
void test(){cout << "2"<< endl;}
g++ -shared -o 1.dll 1.cpp >> 1.dll
g++ -shared -o 2.dll 2.cpp >> 2.dll
g++ -o main.exe 1.dll 2.dll main.cpp >> main.exe
// 2.dll 的 test 会覆盖 1.dll 中的同名函数
// 2.dll 的文件名信息也在 main.exe中,不能修改,修改了就找不到
// 1.dll 文件中的函数如果被 2.dll 完全覆盖,那么可以删除 1.dll
// 1.dll 文件中的函数如果只有被 2.dll 部分覆盖,那么 1.dll 必须存在,且文件名必须是 1.dll
参考
【关于CMake、CMakeLists.txt】相关知识:https://blog.csdn.net/weixin_46135347/article/details/122172470