[linux仓库]程序员的自我修养:彻底弄懂Linux下的动静态库

🌟 各位看官好,我是egoist2023

🌍 Linux == Linux is not Unix !

🚀 今天来学习动静态库的相关知识。

👍 如果觉得这篇文章有帮助,欢迎您一键三连,分享给更多人哦!

目录

什么是库

制作库

源代码

.o文件

静态库

-l 库名称

-L库文件路径 

-I搜索头文件路径

重谈库的定义

动态库

/lb/libmyc.so 

软链接

配置LD_LIBRARY_PATH环境变量

更改系统配置文件

扩展

同时存在,动态链接

同时存在,静态链接

静态库,动态链接

动态库,静态链接

使用外部库

总结


什么是库

库是写好的现有的,成熟的,可以复⽤的代码。现实中每个程序都要依赖很多基础的底层库,不可能每个⼈的代码都从零开始,因此库的存在意义非同寻常.
动静态库实际是Linux下指定目录下的普通文件, 可以被操作系统载⼊内存执⾏。
  • 静态库 .a[Linux].lib[windows]
  • 动态库 .so[Linux]、.dll[windows]

我们把.o称为可重定位目标文件.那么什么叫可重定位呢?这里先抛出这个概念,留在之后解惑. 

制作库

在制作库中围绕开发者和使用者角度进行展开:

  • 从库的开发者来说,提供简洁易用的接口,避免过度设计;
  • 从库的使用者来说,必须是能够简单上手的,不需要自己多写一份代码.

这里讲一个小故事对制作库进行展开:

大学中大部分都接触过C语言课程设计报告,而张三(C语言学的很好)和李四是同班同学且也是关系很铁的宿友.某天,他们的C语言老师王五要求他们提交课设代码(完成两个数字相加)给他看.

作业一发布,张三很快就写好了,并将头源文件进行了分离;但李四内丝毫没有头绪,于是就想到了自己那个C语言考99分的宿友张三,跟他说:兄弟啊!这几天王老师不是布置了个C语言作业吗?但是我有些没头绪,可以借下你的代码看看吗?张三那肯定也是没有犹豫的,于是他有三种方法可以提供给李四.

源代码

张三回到自己的电脑桌前,直接将自己的源代码拷给了李四,跟李四说:我的源代码已经发给你了,但是怕老师可能会发现是我给你的源代码,你把里面的变量名改下,以防被发现.最后执行这条指令就可以跑通程序.

gcc -o myexe *.c

   

.o文件

但是这个老师可不简单,如果给我发现你们的代码实现逻辑是类似的,直接给你们扣学分.

这时张三就慌了啊!跟李四说:这老师贼心挺重,没办法兄弟,要不这样我只给你提供.o和.h文件,并按照我发给你的指令给他看你的实现结果就好.张三自己的.c文件编译形成了.o文件和.h发给了李四.

gcc -o myexe *.o

   

静态库

突然王五老师又布置了个作业,这次作业是模拟实现库文件的打开、写入、关闭,并且不要让我发现你们的测试用例也是一样的!!!

张三也是很快的写好了,但里面含有十多个.c文件.此时李四又来找张三要了,张三依旧怕自己被老师发现,依旧形成.o文件和.h文件一同发给了李四.但李四看到这么多.o文件就头痛,跟张三说有没有简单的方法呢?可以变成一个简单上手的文件吗?

这时张三又在冥思苦想:把多个.o文件封装成一个,同时又要不把自己的实现方法暴露出来?

把多个.o进行打包成静态库啊!,告诉李四自己需要调用哪些方法就行了.

静态库的本质:库源代码形成的obj进行打包.

如何进行打包呢?

ar命令专门用来形成静态库的命令. gnu 归档⼯具:

rc表示(replace and create)

t: 列出静态库中的⽂件
v:verbose 详细信息

张三把自己的静态库.a文件和.h文件一同发给了李四. 

   

-l 库名称

此时李四调用了方法后,用gcc命令链接形成myexe可执行程序时却出现了问题:

  

没有找到这些函数的定义?于是张三指出没有指定库名称.那该如何指定库名称呢?

用 -l + 库名称(去掉lib和后缀.a) 指定库名称

  

果然成了,但是李四说为什么我平常使用C库时不用指定库名称呢?不用带-lc呢?

张三说道:你以为gcc是干啥的?专门用来编译c语言的默认就认识!!!

 输出结论:如果使用任何第三方库,至少需要使用-I表明库名称


-L库文件路径 

但是我们依旧可以看到指定库名称后还是不能形成可执行程序,报错显示该没有找到该库路径.

使用 -L + 路径 来指明路径:

  

形成了可执行程序!!!

于是李四又问:为什么C库不需要指明路径呢?

张三说到:这是因为我们自己的库没有在系统的默认路径下.

gcc/g++编译器找库,一般会在默认路径下查找!

 输出结论:因此所谓的库的安装,就是把库文件拷贝到系统默认路径下/lib64

那么如果我们把自己的静态库拷贝到默认路径下是不是就可以不用指定路径:

此时再次进行链接总算形成了可执行程序:

   

最佳实践:gcc -o myexe  库  -L+指明路径  -l+库名称

我们之前说过如果是包自己的头文件是使用 " " ,而不能像C库一样使用 < > ,那如果也想像库一样使用< > 呢? 

 

 指定一个搜索路径,表明头文件搜索路径,include 

但这样还不够方便,可以不指名.h路径吗?我们知道C头文件肯定放在某个特定文件中:

那么把我们自己的头文件放到这里面应该也是可以的: 

-I搜索头文件路径

  • 静态库(.a):程序在编译链接的时候把库的代码链接到可执⾏⽂件中,程序运⾏的时候将不再需要静态库。
  • ⼀个可执行程序可能⽤到许多的库,这些库运⾏有的是静态库,有的是动态库,⽽我们的编译默认为动态链接库,只有在该库下找不到动态.so的时候才会采⽤同名静态库。我们也可以使⽤ gcc的 -static 强转设置链接静态库。

这个命令用来查看自己以来的动态库: 

   

可以看见我们的libmyc.a没有在里面,即查不到静态库的依赖,因为静态库把自己的代码合并到里面了.如何证明呢?  

既然可以将库是静态连接的,那么C库也是同样可以的:

-static表明是静态连接.再次ldd查看该可执行程序依赖的动态库,这时就没有了.

  

但是内,此时王老师又布置了学生管理系统的作业,而此时张三又要重复一次把自己的.a文件和.h文件拷贝一份给李四,太麻烦了吧?这时之前学过的make/Makefile就有了大作用!!!

make:将多个.c文件编译形成.o文件,再将.o文件通过ar指令打包成一个静态库libmy.c

make output:新建目录mylibc、mylibc/include和mylibc/lib,拷贝.h和.a文件分别到mylibc/include和mylibc/lib,对mylibc目录下的文件进行打包为.tgz压缩文件

make clean: 删除以.a .o为后缀、mylibc目录和mylibc .tgz压缩文件

libmyc.a:mymath.o mystdio.o
	ar -rc $@ $^
%.o:%.c
	gcc -c $<

.PHONY:clean
clean:
	rm -rf *.a *.o mylibc *.tgz

.PHONY:output
output:
	mkdir mylibc
	mkdir -p mylibc/include
	mkdir -p mylibc/lib
	cp *.h mylibc/include
	cp *.a mylibc/lib
	tar czf mylibc.tgz mylibc

于是张三通过make、make output生成了mylibc.tgz压缩文件,把这个压缩文件拷贝给李四,跟李四说你按照我给你的指令进行解压缩,再按照指定头文件路径、库名称和指定库路径的方式进行链接成可执行程序就可以了.

进行解包:

gcc指定头文件路径+库文件路径+库名称: 

重谈库的定义

什么是库?

库=头文件 +库文件
库使用需要搜索 ,1.头文件(-1,大i) 2.库路径(-L) 3.库是谁(-1,小I)

库 如果 不想过多使用上面的选项,则搬到系统特定路径
库安装: 本质是把头文件(/usr/include) 和库文件(/ib64)拷贝到系统的指定的、默认的路径下,编译器能找到的路径下!!!

动态库

  • 动态库(.so):程序在运⾏的时候才去链接动态库的代码,多个程序共享使用库的代码。
  • ⼀个与动态库链接的可执⾏⽂件仅仅包含它⽤到的函数⼊⼝地址的⼀个表,⽽不是外部函数所在⽬标⽂件的整个机器码
  • 在可执行⽂件开始运⾏以前,外部函数的机器码由操作系统从磁盘上的该动态库中复制到内存中,这个过程称为动态链接(dynamic linking)
  • 动态库可以在多个程序间共享,所以动态链接使得可执⾏⽂件更⼩,节省了磁盘空间。操作系统采⽤虚拟内存机制允许物理内存中的⼀份动态库被要⽤到该库的所有进程共⽤,节省了内存和磁盘空间。
  1. gcc -fPIC -c *.c 把.c文件形成.o文件
  2. gcc -o libmyc.so *.o -shared 将.o文件进行动态打包
  • shared: 表⽰⽣成共享库格式
  • fPIC:产⽣位置⽆关码(position independent code)
  • 库名规则:libxxx.so

Make/Makefile方法对.tgz压缩文件进行解包:

libmyc.so:mymath.o mystdio.o
	gcc -o $@ $^ -shared
%.o:%.c
	gcc -fPIC -c $<

.PHONY:clean
clean:
	rm -rf *.so *.o mylibc *.tgz

.PHONY:output
output:
	mkdir mylibc
	mkdir -p mylibc/include
	mkdir -p mylibc/lib
	cp *.h mylibc/include
	cp *.so mylibc/lib
	tar czf mylibc.tgz mylibc

  

当我们执行形成的可执行程序时:却报错了,这是为什么呢?

根据前面所学我不是已经告诉系统或加载器,我的库在哪个路径下了么?你睁眼就报个错?

这是因为你上述操作是只告诉了编译器,什么意思呢?

编译器(如 gcc)在编译时只会把“需要链接哪些库”的信息写进可执行文件(比如 a.out),不会把库本身打包进去。(而我们静态库是把.o文件融进可执行程序里了,静态库删掉,程序照样可以运行)

此时形成了可执行程序,但是这个程序运行时,是由OS的动态链接器(如 ld-linux-x86-64.so.2)去查找这些库,已经跟编译器没有关系了!!!

  

/lb/libmyc.so 

那为什么只有我们自己的库OS动态链接器会找不到?而C语言的却可以,毫无疑问C肯定也是放在了某个特定目录下:

运行的时候,查找动态库,加载器或者0S可以直接去默认路径下查找/lib

那么把我们自己的.so拷贝到Iib就行了,但是这种方式极其不优雅,有时还是有风险的!

软链接

软连接的方式链接/lb/libmyc.so:

配置LD_LIBRARY_PATH环境变量

导入环境变量:export LD_LIBRARY_PATH=$LD_LIBRARY_PATH: /home/egoist/note/note26/other/mylibc/ lib/

更改系统配置文件

动态库查找路径配置文件一般放在:/etc/ld.so.conf.d/里,将自己的库配置进去.

执行ldconfig,重新加载库搜索路径 

此时再执行发现可以运行了:

扩展

同时存在,动态链接

动态和静态库,同时存在的时候,gcc/g++默认优先使用动态库,默认进行动态链接!

同时存在,静态链接

-static:要求我们必须采用静态链接的方案

静态库,动态链接

一个可执行程序,可能会依赖多个库,如果我们只提供静态库,即使我们是动态链接,gcc也没有办法,只能对只提供静态库的库,进行静态链接

动态库,静态链接

使用-static时要求静态库必须存在

 我们的指令是用C写的,那么也是依赖于C语言库的:

  

使用外部库

现在没接触过太多的库,唯⼀接触过的就是C、C++标准库,这⾥推荐⼀个好玩的图形库:ncurses
// 安装
// Centos
    $ sudo yum install -y ncurses-devel
// ubuntu
    $ sudo apt install -y libncurses-dev
系统中其实有很多库,它们通常由⼀组互相关联的⽤来完成某项常⻅⼯作的函数构成。⽐如⽤来处理屏幕显示情况的函数(ncurses库)
#include <stdio.h>
#include <string.h>
#include <ncurses.h>
#include <unistd.h>
#define PROGRESS_BAR_WIDTH 30
#define BORDER_PADDING 2
#define WINDOW_WIDTH (PROGRESS_BAR_WIDTH + 2 * BORDER_PADDING + 2) // 加边框的宽度
#define WINDOW_HEIGHT 5
#define PROGRESS_INCREMENT 3
#define DELAY 300000 // 微秒(300毫秒)

int main()
{
    initscr();
    start_color();
    init_pair(1, COLOR_GREEN, COLOR_BLACK); // 已完成部分:绿⾊前景,⿊⾊背景
    init_pair(2, COLOR_RED, COLOR_BLACK);   // 剩余部分(虽然⽤红⾊可能不太合适,但为演示目的):红色背景
    cbreak();
    noecho();
    curs_set(FALSE);
    int max_y, max_x;
    getmaxyx(stdscr, max_y, max_x);
    int start_y = (max_y - WINDOW_HEIGHT) / 2;
    int start_x = (max_x - WINDOW_WIDTH) / 2;
    WINDOW *win = newwin(WINDOW_HEIGHT, WINDOW_WIDTH, start_y, start_x);
    box(win, 0, 0); // 加边框
    wrefresh(win);
    int progress = 0;
    int max_progress = PROGRESS_BAR_WIDTH;
    while (progress <= max_progress)
    {
        werase(win); // 清除窗⼝内容
        // 计算已完成的进度和剩余的进度
        int completed = progress;
        int remaining = max_progress - progress;
        // 显⽰进度条
        int bar_x = BORDER_PADDING + 1; // 进度条在窗⼝中的x坐标
        int bar_y = 1;                  // 进度条在窗⼝中的y坐标(居中)
        // 已完成部分
        attron(COLOR_PAIR(1));
        for (int i = 0; i < completed; i++)
        {
            mvwprintw(win, bar_y, bar_x + i, "#");
        }
        attroff(COLOR_PAIR(1));
        // 剩余部分(⽤背景⾊填充)
        attron(A_BOLD | COLOR_PAIR(2)); // 加粗并设置背景⾊为红⾊(仅⽤于演⽰)
        for (int i = completed; i < max_progress; i++)
        {
            mvwprintw(win, bar_y, bar_x + i, " ");
        }
        attroff(A_BOLD | COLOR_PAIR(2));
        // 显⽰百分⽐
        char percent_str[10];
        snprintf(percent_str, sizeof(percent_str), "%d%%", (progress * 100) / max_progress);
        int percent_x = (WINDOW_WIDTH - strlen(percent_str)) / 2; // 居中显⽰
        mvwprintw(win, WINDOW_HEIGHT - 1, percent_x, percent_str);
        wrefresh(win); // 刷新窗⼝以显⽰更新

        // 增加进度
        progress += PROGRESS_INCREMENT;

        // 延迟⼀段时间
        usleep(DELAY);
    }

    // 清理并退出ncurses模式
    delwin(win);
    endwin();
    return 0;
}

总结

本文介绍了Linux系统中的动静态库概念及使用方法。从开发者角度讲解了如何制作静态库(.a)和动态库(.so),包括ar命令打包、gcc编译选项等关键技术。通过张三和李四的案例,生动演示了从源代码到.o文件再到静态库的制作过程,并详细说明了-l、-L、-I等关键参数的使用场景。特别强调了动态库运行时查找路径的配置方法,包括环境变量LD_LIBRARY_PATH和系统配置文件设置。文章还对比了动静态库的特点,指出默认优先使用动态链接的规则,并介绍了强制静态链接的方法。最后以ncurses图形库为例,展示了外部库的实际应用。

    

评论 82
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值