【Linux】动静态库

静态库与动态库的概念

  • 静态库(.a):程序在编译链接的时候把库的代码链接到可执行文件中。程序运行的时候将不再需要静态库。
  • 动态库(.so):程序在运行的时候才去链接动态库的代码,多个程序共享使用库的代码。

设计静态库

编写一个简单库,实现加减乘除的算法

cpp 复制代码
// mymath.h文件
#pragma once

#include<stdio.h>

extern int myerrno;

int add(int x, int y);
int sub(int x, int y);
int mul(int x, int y);
int div(int x, int y);

// mymath.c文件
#include"mymath.h"

int myerrno = 0;

int add(int x, int y)
{
	return x + y;	
}

int sub(int x, int y)
{
	return x - y;
}

int mul(int x, int y)
{
	return x * y;	
}

int div(int x, int y)
{
	if(y == 0)
	{
		myerrno = 1;
		return -1;
	}
	return x / y;
}

当需要将自己实现的算法给别人使用的方法:

  • 将源文件直接给别人(暴露源文件内容)、
  • 把源文件的代码打包成库(不会暴露源文件内容),给别人提供的代码是 = 库 + .h

【注意】必须提供库(也就是头文件),库相当于说明书

形成静态库

生成静态库的方式就是将.c文件形成.o文件,再将.o文件打包形成.a静态库,即可使用静态库。

cpp 复制代码
// makefile文件
lib=libmymath.a

$(lib):mymath.o
	ar -rc $@ $^
mymath.o:mymath.c
	gcc -c $^

.PHONY:clean
clean:
	rm -f *.o *.a

指令:ar -rc xxx.a xxx.o xxx.o

【功能】ar是生产静态库的一个命令,-rc代表replace and creat,意思是将.o文件放在.a的二进制文件里面

此时形成mymath.o文件,然后将mymath.o文件打包形成libmymath.a文件

发布静态库

在makefile文件中编辑:

  • .PHONY:output
  • output:
  • mkdir -p lib/include
  • mkdir -p lib/mymathlib
  • cp *.h lib/include
  • cp *.a lib/mymathlib

创建一个目录,将.h文件和.a文件放在lib文件中,如果别人需要使用该库,直接将lib文件夹给别人即可。

bash 复制代码
lib=libmymath.a

$(lib):mymath.o
	ar -rc $@ $^
mymath.o:mymath.c
	gcc -c $^

.PHONY:clean
clean:
	rm -rf *.o *.a lib

.PHONY:output
output:
	mkdir -p lib/include
	mkdir -p lib/mymathlib
	cp *.h lib/include
	cp *.a lib/mymathlib

使用静态库

cpp 复制代码
#include "mymath.h"

int main()
{
	printf("1+2=%d\n", add(1,2));
	return 0;
}

【错误】头文件找不到

使用#include"mymath.h",系统会默认

  1. 在/usr/include/系统路径下寻找。
  2. 当前目录寻找。

显然,我们目前的mymath.h既不在当前目录,也不在系统目录中。

找到头文件的方法:

指令: gcc main.c -I [path]

【功能】gcc带 -I 选项,意思让include除了在当前目录下和系统目录下寻找,还在指定目录下寻找,-I代表include的意思

【错误】找不到add这个函数的实现,这种错误发生在链接时报错,因为.o文件是可以出来的。

找不到函数的链接,是找不到静态库.a文件,系统会默认在:

  • 系统目录/lib64/libc.so路径下找静态库文件
  • 系统目录/lib64/libc.a路径下找静态库文件
  • 当前目录下找静态库文件

找到静态库文件的方法:

指令:gcc main.c -I [path] -L [path]

【功能】-L在指定路径下找静态库文件,-L代表lib的意思

【错误】./lib/mymathlib/目录下不仅仅会存在一个静态库文件,需要显式的说明需要链接哪一个静态库。而./lib/include没有明确说明需要哪一个头文件,是因为在main.c文件中已经显示说明需要使用哪一个头文件。

指令:gcc main.c -I [path] -L [path] -l[静态库]

【功能】-l 代表link的意思,会找指定的静态库

【注意】静态库的真实名称式去掉前缀lib和后缀.a或者.so的名称

总结通过指令的方法使用静态库

  • 指令:gcc main.c -I [path] -L [path] -l[静态库 舍去lib前缀和.a/.so后缀]

【注意】使用第三方库,必须使用-l带第三方库

特殊情况分析

当在main.c文件编写代码时,出现这种情况:

cpp 复制代码
#include "mymath.h"

int main()
{
	printf("10/0=%d\n", div(10,0));
	return 0;
}

【错误分析】此时计算结果为-1,无法清楚的知道是否在调用函数出错,还是本来就是该计算结果。

cpp 复制代码
  1 #include"mymath.h"
  2 
  3 int myerrno = 0;
  4 
  5 int add(int x, int y)
  6 {
  7     return x + y;
  8 }
  9 
 10 int sub(int x, int y)
 11 {
 12     return x - y;
 13 }
 14 
 15 int mul(int x, int y)
 16 {
 17     return x * y;
 18 }
 19 
 20 int div(int x, int y)
 21 {
 22     if(y == 0)
 23     {
 24         myerrno = 1;
 25         return -1;
 26     }
 27     return x / y;
 28 }
cpp 复制代码
#include "mymath.h"

int main()
{
	int n = div(10, 0);
	printf("10/0=%d, myerrno=%d\n", n, myerrno);
	return 0;
}

可以通过设置errno的方式来解决此问题,当errno为1说明结果是错误的,当errno为0说明结果是正确的。

【结论】

  1. 第三方库,在使用的时候,必须使用gcc -l
  2. errno的本质就是全局变量。
  3. gcc 默认是动态链接,但是如果在找的地方只有静态库,gcc 会选择使用静态链接,-static选项是一种建议选项。系统中动态库和静态库均提供时,默认会选择动态链接,带选项-static会使用静态链接,当系统中只存在动态库,不管有没有static选项都会动态链接。当只存在静态库时,只会静态链接。
  4. 如果系统中需要链接多个库,那么gcc可以链接多个库

将头文件和库文件放在系统文件下

指令:sudo cp 当前目录下的头文件 /usr/include

【功能】将头文件保存在系统头文件目录

指令:sudo cp 当前目录下的库文件 /usr/lib64

【功能】将头文件保存在系统库文件目录

此时,只需要链接需要哪个库文件即可。

【说明】将头文件或者库文件拷贝到系统路径下,本质是在进行库的安装。

使用软连接的方式

指令:ln -s [目录名称] [软连接名称]

【说明】将需要使用的头文件或者库文件建立软连接。

cpp 复制代码
dabai@VM-16-7-ubuntu:~/git/linux/1218/test$ sudo ln -s /home/dabai/git/linux/1218/test/lib/include/ /usr/include/myinc
dabai@VM-16-7-ubuntu:~/git/linux/1218/test$ ls /usr/include/myinc
mymath.h
dabai@VM-16-7-ubuntu:~/git/linux/1218/test$ ls -l /usr/include/myinc
lrwxrwxrwx 1 root root 44 Dec 19 10:00 /usr/include/myinc -> /home/dabai/git/linux/1218/test/lib/include/
cpp 复制代码
#include "myinc/mymath.h"

int main()
{
	int n = div(10, 0);
	printf("10/0=%d, myerrno=%d\n", n, myerrno);
	return 0;
}

使用绝对路径的方式,在系统路径下设置软链接,通过软链接找mymath.h头文件。

cpp 复制代码
dabai@VM-16-7-ubuntu:~/git/linux/1218/test$ sudo ln -s /home/dabai/git/linux/1218/test/lib/mymathlib/ /lib64/libmymath.a
dabai@VM-16-7-ubuntu:~/git/linux/1218/test$ ls -l /lib64/libmymath.a 
lrwxrwxrwx 1 root root 46 Dec 19 10:08 /lib64/libmymath.a -> /home/dabai/git/linux/1218/test/lib/mymathlib/

使用绝对路径的方式,在系统路径下设置软链接,通过软链接找库文件。

设计动态库

设置四个文件,形成动态库

myprint.*文件

cpp 复制代码
// myprintf.h
#pragma once
#include<stdio.h>

void Print();

// myprintf.c

#include "myprintf.h"

void Print()
{
	printf("hello new world!\n");
	printf("hello new world!\n");
	printf("hello new world!\n");
	printf("hello new world!\n");
	printf("hello new world!\n");
}

mylog.*文件

cpp 复制代码
// mylog.h文件
#pragma once

#include<stdio.h>

void Log(const char*);

// mylog.c

#include "mylog.h"


void Log(const char* info)
{
	printf("Warning :%s\n", info);
}

形成动态库

通过指令的方式形成动态库

指令:

  1. gcc -fPIC -c [filename]
  2. gcc -shared -o lib[filename].so

【功能】

  • fPIC:产生位置无关码
  • -shared:形成共享库格式

首先将.c文件形成.0文件

然后将.o文件打包,形成动态库

使用makefile的方式形成动态库

makefile文件

cpp 复制代码
dy-lib=libmymethod.so
static-lib=libmymath.a

.PHONY:all
all: $(dy-lib) $(static-lib)
	
$(static-lib):mymath.o
	ar -rc $@ $^
mymath.o:mymath.c
	gcc -c $^

$(dy-lib):mylog.o myprintf.o
	gcc -shared -o $@ $^
mylog.o:mylog.c
	gcc -fPIC -c $^
myprintf.o:myprintf.c
	gcc -fPIC -c $^


.PHONY:clean
clean:
	rm -rf *.o *.a *.so lib

.PHONY:output
output:
	mkdir -p mylib/include
	mkdir -p mylib/lib
	cp *.h mylib/include
	cp *.a mylib/lib
	cp *.so mylib/lib

使用动态库

将mylib目录放在test目录下面,让test进行使用。

编辑main.c文件

cpp 复制代码
#include "mylog.h"
#include "myprintf.h"

int main()
{
	Print();
	Log("hello log function");

	return 0;
}

指令:gcc main.c -I mylib/include/ -L mylib/lib/

【功能】将动态库链接

指令:gcc main.c -I mylib/include/ -L mylib/lib/ -lmymethod -lmymath

【功能】将动态库和静态库都链接,这里如果需要添加库,只需要在后面加 -l...

cpp 复制代码
dabai@VM-16-7-ubuntu:~/git/linux/1218/test$ gcc main.c -I mylib/include/ -L mylib/lib/ -lmymethod
dabai@VM-16-7-ubuntu:~/git/linux/1218/test$ ./a.out 
./a.out: error while loading shared libraries: libmymethod.so: cannot open shared object file: No such file or directory

链接动态库时会出现上述问题,问题出现的原因是,编译器找到动态库,但是加载器没有找到动态库:

指令:ldd [可执行文件]

【功能】查看动态链接

方法一:在系统共享库处建立软连接

cpp 复制代码
sudo ln -s /home/dabai/git/linux/1218/test/mylib/lib/libmymethod.so /lib64/libmymethod.so

如果出现ldd发现没有链接成功,则输入指令:

复制代码
# 直接指定软链接所在路径(/lib64已在默认搜索路径,但需缓存,此方式绕开缓存)
export LD_LIBRARY_PATH=/lib64:$LD_LIBRARY_PATH

此时,即可使用动态库:

方法二:拷贝到系统默认路径即可

该方法与静态库方法类似

方法三:添加到系统环境变量

复制代码
# 直接追加你的库路径(推荐,不覆盖原有环境变量)
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/dabai/git/linux/1218/test/mylib/lib

将自己的库所在的路径,添加到系统的环境变量中。

方法四:在/etc/ld.so.conf.d/下创建自定义配置文件,指定库路径

复制代码
sudo echo "/home/dabai/git/linux/1218/test/mylib/lib" > /etc/ld.so.conf.d/mymethod.conf

【总结】动态库在进程运行的时候,是要被加载的,而静态库不需要。常见的动态库被所有的可执行程序(动态链接的),都要使用动态库------共享库,所以,动态库在系统中加载只会,会被所有进程共享。

动态库是怎么被加载的

动态库也是文件,动态库加载到物理内存中,与页表建立映射,保存在进程地址空间中的共享区(栈区与堆区之间的区域)。当建立映射之后,执行的任何代码,都是在进程地址空间中执行的。

而系统在运行中,一定会存在多个动态库,这些动态库会被操作系统管理起来(先描述再组织),也就是说操作系统了解所有库的加载情况。当存在多个进程需要使用一个库的情况时,操作系统会分析物理内存中是否存在该库,如果存在,直接会让这些进程直接建立联系,将库的代码通过页表映射保存在共享库中,也就说在物理内存中只需要保存一份库的代码即可,这个库也被称为动态库/共享库。

++问题:当动态库中设置全局变量时,例如前面的errno,多个进程是如何处理的:++

【答】:库是在堆区和栈区之间的共享区的,当存在对全局变量进行写入的时候,会造成写时拷贝。

关于地址相关的知识

程序没有加载前的地址(程序)

程序在编译好之后,程序内部有地址的概念,是因为编译器也需要考虑操作系统,这些地址已经是虚拟地址,但是为了区分虚拟地址空间的地址,将程序刚编译好的地址称为逻辑地址,逻辑地址是磁盘中程序编译后的地址。

【注意】可程序程序在编译后就存在地址,该地址称为逻辑地址。

cpp 复制代码
#include<stdio.h>

int a = 10;
int b = 20;

int main()
{
	printf("hello world!\n");
	
	int x = 30;
	int y = 40;
	int z = x + y;

	printf("result: %d\n", z);

	return 0;
}

指令:objdump -S a.out

【功能】将a.out程序进行反汇编。

【说明】这些指令都是CPU进行执行的。

程序加载后的地址(进程)

将可执行程序加载到内存的时候,每条指令都占据一定的物理地址,而每条指令自己内部又保存着自己的逻辑地址。

当可执行程序编译好之后,会在代码的开头存放一个入口地址,这个入口地址不是物理地址,而是逻辑地址。在CPU中会存在一个寄存器,寄存器将读取入口地址(逻辑地址),然后找到虚拟地址空间的起始地址,然后通过页表映射,找到物理内存中的代码。

在CPU中读取到指令的时候,可以存在数据,也可能存在地址,这个地址是逻辑地址,然后寄存器会在虚拟地址空间中查找该逻辑地址,通过页表映射再找到物理地址,当物理地址不存在时,会进行缺页中断。

【注意】在代码中读取到的地址都是逻辑地址,通过逻辑地址和页表的映射可以找到函数的物理地址。

动态库的地址

将动态库放到物理内存中,通过页表映射会放在进程地址空间中的共享库的位置。而共享库很大,动态库被加载到固定地址空间中的位置是不可能的。动态库被加载的时候,库可以在虚拟内存中,任意位置加载。

所以动态库会在自己内部函数不采用绝对编址,而是采用每个函数在库中的偏移量即可。

  • -fPIC:产生位置无关码

该选项的作用是直接用偏移量进行对库中的函数进行编址。

【问题】静态库为什么不谈加载,不谈与位置无关?这是因为静态库会直接将代码加载到可执行程序中。静态库的代码会放在进程地址空间中的代码区中,以固定地址的方式。

相关推荐
草莓熊Lotso2 小时前
Linux 实战:从零实现动态进度条(含缓冲区原理与多版本优化)
linux·运维·服务器·c++·人工智能·centos·进度条
dishugj9 小时前
【linux】Redhat 6.3系统安装zabbix-agent软件包,无法使用YUM源问题
linux·运维·zabbix
无奈笑天下10 小时前
【麒麟镜像vmtools异常排查指导书】
linux·运维·经验分享·云计算·kylin
Xの哲學10 小时前
Linux多级时间轮:高精度定时器的艺术与科学
linux·服务器·网络·算法·边缘计算
QT 小鲜肉11 小时前
【Linux命令大全】001.文件管理之mmove命令(实操篇)
linux·服务器·前端·chrome·笔记
Winner130011 小时前
查看rk3566摄像头设备、能力、支持格式
linux·网络·人工智能
QT 小鲜肉11 小时前
【Linux命令大全】001.文件管理之mdel命令(实操篇)
linux·运维·服务器·chrome·笔记
大聪明-PLUS12 小时前
如何从零开始开发 Linux 驱动程序
linux·嵌入式·arm·smarc
物随心转12 小时前
input子系统工作原理
linux