欢迎光临
我们一直在努力

【Linux】动静态库

静态库与动态库的概念

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

设计静态库

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

// 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静态库,即可使用静态库。

// 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文件夹给别人即可。

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

使用静态库

#include "mymath.h"

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

【错误】头文件找不到

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

  • 在/usr/include/系统路径下寻找。
  • 当前目录寻找。
  • 显然,我们目前的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文件编写代码时,出现这种情况:

    #include "mymath.h"

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

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

    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 }

    #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说明结果是正确的。

    【结论】

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

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

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

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

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

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

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

    使用软连接的方式

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

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

    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/

    #include "myinc/mymath.h"

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

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

    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.*文件

    // 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.*文件

    // 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);
    }

    形成动态库

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

    指令:

  • gcc -fPIC -c [filename]
  • gcc -shared -o lib[filename].so
  • 【功能】

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

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

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

    使用makefile的方式形成动态库

    makefile文件

    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文件

    #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…

    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 [可执行文件]

    【功能】查看动态链接

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

    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,多个进程是如何处理的:

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

    关于地址相关的知识

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

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

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

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

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

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

    赞(0)
    未经允许不得转载:171主机测评 » 【Linux】动静态库
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址