【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 libfilename.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:产生位置无关码

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

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

相关推荐
A小辣椒2 天前
TShark:Wireshark CLI 功能
linux
A小辣椒2 天前
TShark:基础知识
linux
AlfredZhao2 天前
OCI 明明分配了 200G 系统盘,为什么 df 只看到 30G?
linux·oci
AlfredZhao3 天前
vi 删除指定范围的行,不用再反复按 dd
linux·vi
用户9718356334663 天前
银河麒麟 KY10 申威(SW64) 安装 nginx-1.16.1-2.p01.ky10.sw_64.rpm 详细步骤
linux
猪脚踏浪3 天前
linux 拷贝文件或目录到指定的位置
linux
摇滚侠4 天前
Linux CentOS7 rpm 安装 MySQL 5.7
linux·运维·mysql
bush44 天前
嵌入式linux学习记录十四、术语
linux·嵌入式
载数而行5204 天前
Linux 11 动态监控指令top
linux
不会C语言的男孩4 天前
Linux 系统编程 · 第 8 章:进程基础
linux·c语言