静态库与动态库的制作与使用(在manjaro上遇到的问题与解决)

什么是库

  • 库文件是计算机上的一类文件,可以简单的把库文件看成是一种代码仓库,它提供给使用者一些可以直接拿来用的变量、函数或类。
  • 库是一种特殊的程序,编写库的程序和编写一般的程序区别不大,只是库不能单独运行。
  • 库文件有两种,静态库和动态库(共享库),区别是:静态库在程序的链接阶段被复制到了程序中;动态库在链接阶段没有被复制到程序中,而是程序在运行时由系统动态加载到内存中供程序调用。
  • 库的好处:
    • 1、代码保密
    • 2、方便部署和分发

静态库与动态库的工作原理

  • 静态库:GCC 进行链接时,会把静态库中代码打包到可执行程序中
  • 动态库:GCC 进行链接时,动态库的代码不会被打包到可执行程序中
  • 程序启动之后,动态库会被动态加载到内存中,通过 ldd (list dynamic dependencies)命令检查动态库依赖关系
  • 如何定位共享库文件呢?
    当系统加载可执行代码时候,能够知道其所依赖的库的名字,但是还需要知道绝对路径。此时就需要系统的动态载入器来获取该绝对路径。对于elf格式的可执行程序,是由ld-linux.so来完成的,它先后搜索elf文件的 DT_RPATH段 (虚拟地址空间)——> 环境变量LD_LIBRARY_PATH ——> /etc/ld.so.cache文件列表 ——> /lib/,/usr/lib目录找到库文件后将其载入内存。(加载时机:比如main需要调用add方法(api)的时候,就需要去加载)

ldd的使用

(0x00007ffdde9ec000) 内存地址
ld-linux-x86-x64.so.2 动态载入器

静态库的制作

  • 命名规则:
    • linux:libxxx.a (库文件的名字)
      • lib:前缀(固定)—library缩写
      • xxx:库的名字,自己起
      • .a:后缀(固定)
    • Windows:libxxx.lib
  • 静态库的制作
    • gcc获得.o文件
    • 将.o文件打包,使用ar工具(archive)
      • ar rcs libxxx.a xxx.o xxx.o
        • r - 将文件插入备存文件中
        • c - 建立备存文件
        • s - 索引
gcc -c add.c div.c mult.c sub.c//gcc -c 进行编译生成.o文件
ar rcs libcalc.a add.o sub.o mul//生成静态库

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

静态库的使用

如下图所示,这是我们一个实现加减乘除的项目文件夹。其中lib文件夹下的libcalc.a文件是生成的库文件。在静态库使用的时候,头文件必须放到项目里面。不要以为生成了静态库,就不用头文件了,因为我们生成的库仍然需要头文件。

在这里插入图片描述

gcc main.c -o app		//编译man.c
    /**报错:head.h不存在,所以后续要加上**/
gcc main.c -o app -I ./include/  	//-I 指定包含include文件(头文件head.h)的目录
	/**报错:编译时未找到函数,所以要加载库**/
gcc main.c -o app -I ./include/ -l calc -L ./lib	//-l 指定使用的库  -L 指定库的路径

在这里插入图片描述

#include <stdio.h>
#include "head.h"

int main()
{
    int a = 20;
    int b = 12;
    printf("a = %d, b = %d\n", a, b);
    printf("a + b = %d\n", add(a, b));
    printf("a - b = %d\n", subtract(a, b));
    printf("a * b = %d\n", multiply(a, b));
    printf("a / b = %f\n", divide(a, b));
    return 0;
}

由main函数可知我们没有调用src文件夹下的实现加减乘除的源码,我们删除src文件夹仍然可以运行,这样可以保护我们的源码不被别人知道,实现代码保密。

动态库的制作

  • 命名规则:

    • Linux : libxxx.so
      • lib : 前缀(固定)
      • xxx : 库的名字,自己起
      • .so: 后缀(固定)
      • 在Linux下是一个可执行文件
    • Windows : libxxx.dll
  • 动态库的制作:

  • gcc 得到 .o 文件,得到和位置无关的代码
    - gcc -c –fpic/-fPIC a.c b.c

  • gcc 得到动态库

    • gcc -shared a.o b.o -o libcalc.so
gcc -c -fpic add.c div.c sub.c mult.c		//gcc -c 进行编译生成.o文件		-fpic  用于编译阶段,产生的代码没有绝对地址,全部用相对地址,这正好满足了共享库的要求,共享库被加载时地址不是固定的。如果不加-fpic ,那么生成的代码就会与位置有关,当进程使用该.so文件时都需要重定位,且会产生成该文件的副本,每个副本都不同,不同点取决于该文件代码段与数据段所映射内存的位置。
gcc -shared *.o -o libcalc.so			//生成动态库			

在这里插入图片描述
在这里插入图片描述

动态库的使用

与静态库的使用一样(除了运行时)

gcc main.c -o main		//编译man.c
    /**报错:head.h不存在,所以后续要加上**/
gcc main.c -o main -I ./include/  	//-I 指定包含include文件(头文件head.h)的目录
	/**报错:编译时未找到函数,所以要加载库**/
gcc main.c -o main -I ./include/ -l calc -L ./lib	//-l 指定使用的库  -L 指定库的路径

在这里插入图片描述

动态库加载失败的原因

未加载到内存中,没有指定绝对路径,详情去了解静态库与动态库的工作原理

解决动态库加载失败的问题

修改环境变量LD_LIBRARY_PATH

方法一:(只能加载在一个终端,切换了就没用)

修改宏变量LD_LIBRARY_BATH

//这条命令没用 echo 查找不到(可能是因为在fish下)
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/run/media/root/study/C++work/Linux/DynamicLibrary/library/lib	
//下面这个有用
export LD_LIBRARY_PATH=./lib

在这里插入图片描述

只能在本终端使用,切换终端就不行

在这里插入图片描述

方法二:(用户级别的长久配置)

在用户环境变量中添加LD_LIBRARY_PATH

//编辑
vim .bashrc
//在里面编辑以下命令
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/run/media/root/study/C++work/Linux/DynamicLibrary/library/lib
//执行以启动 生效
source ~/.bashrc
//报错 是因为我是在fish下执行的,得切到bash下去执行 
//切到bash
bash
//切回fish
fish

//又有一个问题,每次开一个终端,都不能自动执行.bashrc,必须手动执行
    

在这里插入图片描述在这里插入图片描述
在这里插入图片描述
在这里插入图片描述在这里插入图片描述

方法三:(系统级别)

使用root用户在etc文件夹中的profile文件里添加环境变量。编辑/etc/profile

/**************下图一*****************/
vim /etc/profile		//编辑文件,将环境变量添加进去
source /etc/profile		//执行
/** 报错 是由于在fish shell下执行的,和方法二的问题一样 ,得切换shell**/
/**************下图二*****************/
bash
//切换shell后 再执行依旧有问题,根据错误信息去更改文件,查找错误(后面两个错误)
/**************下图三*****************/
//更改后的信息依旧报错:bash:append_path:command not found
//搜索该错误:查找到相关解决方法 ----》 https://blog.khmersite.net/2021/12/bash-append-path-command-not-found/
//下面有讲大概的解决方案

/**************最后一张图*****************/
//最终,重新去开一个终端,运行./main,能运行起来
  

红圈标注的显示是fish 所以要切换
在这里插入图片描述

如下图:

切换后发现还是有问题,可能是我的这个文件有问题,根据最后两行的提示去找(我改前没截图,就用代码写吧)

大概解决方案:

  1. 打开此文件,找到相关代码行

    mkdir /run/mysqld
    cp -f /etc/fish/fish.conf.raw  /etc/fish/fish.conf
    
  2. 找到文件路径,在etc/fish/里,发现里面的文件名与代码不符

/******更改后*****/
//删除mkdir /run/mysqld  因为找到文件路径,发现已经存在,便删除了
//找到相关路径,发现.raw后缀文件(config.fish.raw)与配置文件(fish.conf.raw)不一致,将配置文件进行修改,修改后再执行,发现生成的fish.conf与文件系统里的config.fish一致,所以直接将配置文件的fish.conf修改成config.fish
cp -f /etc/fish/config.fish.raw  /etc/fish/config.fish

在这里插入图片描述

如下图:

更改后的信息依旧报错:bash:append_path:command not found
搜索该错误:查找到相关解决方法 ----》 bash:app_path: command not found

在这里插入图片描述

大概的解决方案:

  1. 检查/etc/profile配置文件中的“append"关键字 grep append /etc/profile --color (下面是我自己打开文件找的)

    # Append our default paths
    appendpath () {
        case ":$PATH:" in
            *:"$1":*)
                ;;
            *)
                PATH="${PATH:+$PATH:}$1"
        esac
    }
    
    appendpath '/usr/local/sbin'
    appendpath '/usr/local/bin'
    appendpath '/usr/bin'
    unset appendpath
    
  2. 发现他与报错信息里的append_path不一致,经过搜索发现,基本上,在Arch Linux中对/etc/profile进行了一些更改,这些更改被保存到一个新文件-/etc/profile. pacnew。手动更新/etc/profile由Arch用户决定。

  3. 所以检查这两个文件的不同 diff /etc/profile /etc/profile.pacnew

在这里插入图片描述

6,7c6,8
< # Append our default paths
< appendpath () {
---
> # Append "$1" to $PATH when not already in.
> # This function API is accessible to scripts in /etc/profile.d
> append_path () {
16,19c17,20
< appendpath '/usr/local/sbin'
< appendpath '/usr/local/bin'
< appendpath '/usr/bin'
< unset appendpath
---
> # Append our default paths
> append_path '/usr/local/sbin'
> append_path '/usr/local/bin'
> append_path '/usr/bin'
20a22
> # Force PATH to be environment
31,32c33,42
< # Source global bash config
< if test "$PS1" && test "$BASH" && test -z ${POSIXLY_CORRECT+x} && test -r /etc/bash.bashrc; then
---
> # Unload our profile API functions
> unset -f append_path
> 
> # Source global bash config, when interactive but not posix or sh mode
> if test "$BASH" &&\
>    test "$PS1" &&\
>    test -z "$POSIXLY_CORRECT" &&\
>    test "${0#-}" != sh &&\
>    test -r /etc/bash.bashrc
> then
41,72d50
< export QTWEBENGINE_CHROMIUM_FLAGS="--no-sandbox"
< 
< # Java setting
< #export JAVA_HOME=/usr/lib/jvm/java-11-openjdk/
< #export PATH=$JAVA_HOME/bin:$PATH
< #export CLASSPATH=.:$JAVA_HOME/lib/tool.jar:$JAVA_HOME/lib/dt.jar
< #end
< 
< # Qt6 setting
< #export QTDIR=/opt/Qt6.3.1/6.3.1/gcc_64
< #export PATH=$QTDIR/bin:$PATH
< #export QTINC=$QTDIR/include:$QTINC
< #export QTLIB=$QTDIR/lib:$QTLIB
< #export QT_PLUGIN_PATH=$QTDIR/plugins:$QT_PLUGIN_PATH
< ##export LD_LIBRARY_PATH=$QTDIR/lib:$LD_LIBRARY_PATH
< # Setting FileDialog gtk+ theme
< export QT_QPA_PLATFORMTHEME=gtk2
< #End
< 
< 
< #Android development
< export ANDROID_SDK=/opt/android-sdk
< export PATH=$ANDROID_SDK/platform-tools:$ANDROID_SDK/tools:$PATH
< #export ANDROID_AVD_HOME=/media/study
< modprobe b43
< modprobe fuse
< chown mysql:mysql /run/mysqld
< cp -f /etc/fish/config.fish.raw  /etc/fish/config.fish
< 
< 
< 
< export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/run/media/root/study/C++work/Linux/DynamicLibrary/library/lib

  1. 在我看来,这些文件之间的主要区别只是函数的名称从appendpath()更改为append_path()。我们应该能够用/etc/profile.pacnew’s替换letc/profile的内容。作为良好做法,我首先通过将旧文件重命名为/etc/profile.old来备份旧文件
sudo mv /etc/profile{,.old}
sudo mv /etc/profile{.pacnew,}
  1. 确认上述错误消息不再存在。

     source /etc/profile
    

在这里插入图片描述

**注意:**复制完后如果之前配置文件里加了啥别的记得加回去

更改完配置文件并执行后,再去运行./main

在这里插入图片描述
在这里插入图片描述

修改/etc/ld.so.cache文件列表

vim /etc/ld.so.cache
//里面是二进制文件
//换个方式
vim /etc/ld.so.conf		//将动态库的路径保存进去
sudo ldconfig			//更新保存的信息

在这里插入图片描述

在这里插入图片描述

验证是否配置成功

在这里插入图片描述

将动态库文件放入/lib/或/usr/lib目录里(不推荐使用)

这两个目录本身就包含系统自带的动态库文件,如果放进去,有可能会把系统自带的文件给替换掉

静态库与动态库的对比

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

羊小滑

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值