【Linux学习笔记】深入理解动静态库本质及其制作

【Linux学习笔记】深入理解动静态库本质及其制作

🔥个人主页大白的编程日记

🔥专栏Linux学习笔记


文章目录

  • 【Linux学习笔记】深入理解动静态库本质及其制作
    • 前言
    • [一. 什么是库](#一. 什么是库)
    • [二. 静态库](#二. 静态库)
      • [2.1 静态库生成](#2.1 静态库生成)
      • [2-2 静态库使用](#2-2 静态库使用)
    • 三.动态库
      • [3.1 动态库生成](#3.1 动态库生成)
      • [3-2 动态库使用](#3-2 动态库使用)
      • [3-3 库运行搜索路径](#3-3 库运行搜索路径)
        • [3.3.1 问题](#3.3.1 问题)
        • [3.3.2 解决方案](#3.3.2 解决方案)
    • [四. 使用外部库](#四. 使用外部库)
    • [五. 目标文件](#五. 目标文件)
    • 后言

前言

哈喽,各位小伙伴大家好!上期我们讲了文件系统今天我们讲的深入理解动静态库本质及其制作。话不多说,我们进入正题!向大厂冲锋!

一. 什么是库

库是写好的现有的,成熟的,可以复用的代码。现实中每个程序都要依赖很多基础的底层库,不可能每个人的代码都从零开始,因此库的存在意义非同寻常。

本质上来说库是一种可执行代码的二进制形式,可以被操作系统载入内存执行。库有两种:

  • 静态库 .a[linux].lib[windows]
  • 动态库 .so[linux].dll[windows]
bash 复制代码
// ubuntu 动态库
// C
$ ls -l /lib/x86_64-linux-gnu/libc-2.31.so
-rwxr-xr-x 1 root root 2029592 May 1 02:20 /lib/x86_64-linux-gnu/libc-2.31.so
$ ls -l /lib/x86_64-linux-gnu/libc.a
-rwxr-xr-x 1 root root 5747594 May 1 02:20 /lib/x86_64-linux-gnu/libc.a

// C++
$ ls /usr/lib/gcc/x86_64-linux-gnu/9/libstdc++.so -l
lrwxrwxrwx 1 root root 40 Oct 24 2022 /usr/lib/gcc/x86_64-linux-gnu/9/libstdc++.so -> ../../../x86_64-linux-gnu/libstdc++.so.6
$ ls /usr/lib/gcc/x86_64-linux-gnu/9/libstdc++.a
$ ls /usr/lib/gcc/x86_64-linux-gnu/9/libstdc++.a

// Centos 动静态库
$ / C
$ ls /lib64/libc-2.17.so -l
-rwxr-xr-x 1 root root 2156592 Jun 4 23:05 /lib64/libc-2.17.so
[whb@ite-alicloud ~]$ ls /lib64/libc.a -l

-rwxr-xr-x 1 root root 5105516 Jun 4 23:05 /lib64/libc.a
$ ls /lib64/libstdc++.so.6 -l
lrwxrwxrwx 1 root root 19 Sep 18 20:59 /lib64/libstdc++.so.6 -> libstdc++.so.6.0.19
$ ls /usr/lib/gcc/x86_64-redhat-linux/4.8.2/libstdc++.a -l
-rwxr-xr-x 1 root root 2932366 Sep 30 2020 /usr/lib/gcc/x86_64-redhat-linux/4.8.2/libstdc++.a

准备工作,准备好历史封装的libc代码,在任意新增"库文件

二. 静态库

  • 静态库(.a):程序在编译链接的时候把库的代码链接到可执行文件中,程序运行的时候将不再需要静态库。

  • 一个可执行程序可能用到许多的库,这些库运行有的是静态库,有的是动态库,而我们的编译默认认为动态链接库,只有在该库下找不到动态.so的时候才会采用同名静态库。我们也可以使用gcc的-static强转设置链接静态库。

2.1 静态库生成

bash 复制代码
// Makefile
libmystdio.a:my_stdio.o my_string.o
    ar -rc $@ $^
    echo "build $^ to $@ ... done"

%.o:%.c
    gcc -c $<
    echo "compiling $< to $@ ... done"

.PHONY:clean
clean:
    @rm -rf *.a *.o stdc*
    echo "clean ... done"

.PHONY:output
output:
    @mkdir -p stdc/include
    @mkdir -p stdc/lib
    @cp -f *.h stdc/include
    @cp -f *.a stdc/lib
    @tar -czf stdc.tgz stdc
    echo "output stdc ... done"
  • ar 是 gnu 归档工具,rc 表示(replace and create)
bash 复制代码
$ ar -tv libmystdio.a
rw-rw-r-- 1000/1000  2848 Oct 29 14:35 2024 my_stdio.o
rw-rw-r-- 1000/1000  1272 Oct 29 14:35 2024 my_string.o
  • t:列出静态库中的文件
  • v: verbose 详细信息

2-2 静态库使用

cpp 复制代码
// 任意目录下,新建
// main.c,引入库头文件
c
复制
#include "my_stdio.h"
#include "my_string.h"
#include <stdio.h>

int main()
{
    const char *s = "abcdefg";
    printf("%s: %d\n", s, my_strlen(s));

    FILE *fp = fopen("./log.txt", "a");
    if(fp == NULL) return 1;

    fwrite(s, my_strlen(s), fp);
    fwrite(s, my_strlen(s), fp);
    fwrite(s, my_strlen(s), fp);

    fclose(fp);
    return 0;
}
  • L:指定库路径
  • I:指定头文件搜索路径
  • l:指定库名
  • 测试目标文件生成后,静态库删掉,程序照样可以运行
  • 关于-static选项,稍后介绍
  • 库文件名称和引入库的名称:去掉前缀lib,去掉后缀.so,.a,如:libc.so -> c

三.动态库

  • 动态库(.so):程序在运行的时候才去链接动态库的代码,多个程序共享使用库的代码。
  • 一个与动态库链接的可执行文件仅仅包含它用到的函数入口地址的一个表,而不是外部函数所在目标文件的整个机器码
  • 在可执行文件开始运行以前,外部函数的机器码由操作系统从磁盘上的该动态库中复制到内存中,这个过程称为动态链接(dynamic
    linking)
  • 动态库可以在多个程序间共享,所以动态链接使得可执行文件更小,节省了磁盘空间。操作系统采用虚拟内存机制允许物理内存中的一份动态库被要用到该库的所有进程共用,节省了内存和磁盘空间

3.1 动态库生成

bash 复制代码
libmystdio.so:my_stdio.o my_string.o

    gcc -o $@ $^ -shared

%.o:%.c
    gcc -fPIC -c $<

.PHONY:clean
clean:
    @rm -rf *.so *.o stdc*
    @echo "clean ... done"

.PHONY:output
output:
    @mkdir -p stdc/include
    @mkdir -p stdc/lib
    @cp -f *.h stdc/include
    @cp -f *.so stdc/lib
    @tar -czf stdc.tgz stdc
    @echo "output stdc ... done"
  • shared: 表示生成共享库格式
  • fPIC:产生位置无关码(position independent code)
  • 库名规则:libxxx.so

3-2 动态库使用

bash 复制代码
// 场景1: 头文件和库文件安装到系统路径下
$ gcc main.c -lmystdio

// 场景2: 头文件和库文件和我们自己的源文件在同一个路径下
$ gcc main.c -L. -lmymath

// 场景3: 头文件和库文件有自己的独立路径
bash 复制代码
$ gcc main.c -I头文件路径 -L库文件路径 -lmymath
bash 复制代码
$ gcc main.c -I头文件路径 -L库文件路径 -lmymath
bash 复制代码
$ ldd libmystdio.so  // 查看库或者可执行程序的依赖
bash 复制代码
linux-vdso.so.1 => (0x00007fff4d396000)
libc.so.6 => /lib64/libc.so.6 (0x00007f8917335000)
/lib64/ld-linux-x86-64.so.2 (0x00007f8917905000)
bash 复制代码
$ ll
total 24
-rwxrwxr-x 1 whb whb 8592 Oct 29 14:50 libmystdio.so
-rw-rw-r-- 1 whb whb 359 Oct 19 16:07 main.c
-rw-rw-r-- 1 whb whb 447 Oct 29 14:50 my_stdio.h
-rw-rw-r-- 1 whb whb 447 Oct 29 14:50 my_string.h
$ gcc main.c -L. -lmystdio
$ ll
total 36
-rwxrwxr-x 1 whb whb 8600 Oct 29 14:51 a.out
-rwxrwxr-x 1 whb whb 8592 Oct 29 14:50 libmystdio.so
-rw-rw-r-- 1 whb whb 359 Oct 19 16:07 main.c
-rw-rw-r-- 1 whb whb 447 Oct 29 14:50 my_stdio.h
-rw-rw-r-- 1 whb whb 447 Oct 29 14:50 my_string.h
[whb@ite-alicloud other]$ ./a.out

3-3 库运行搜索路径

3.3.1 问题
bash 复制代码
$ ldd a.out
    linux-vdso.so.1 => (0x00007fff4d396000)
    libc.so.6 => /lib64/libc.so.6 (0x00007f2a2ef30000)
    /lib64/ld-linux-x86-64.so.2 (0x00007f2a2ef20000)
3.3.2 解决方案
  • 拷贝.so文件到系统共享库路径下,一般指/usr/lib、/usr/local/lib、/lib64或者开篇指明的库路径等
  • 向系统共享库路径下建立同名软连接
  • 更改环境变量:LD_LIBRARY_PATH
  • ldconfig方案:配置/etc/ld.so.conf.d/,ldconfig更新

四. 使用外部库

我们现在接触过太多的库,唯一接触过的就是C、C++标准库,这里我们可以推荐一个好玩的图形库:ncurses

cpp 复制代码
// 我们代码是用文心一直生成并调试通过的,具体要什么功能,大家可以试着了解一下
#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 3
#define PROGRESS_INCREMENT 1
#define DELAY 300000 // 微秒(300毫秒)

int main() {
    initscr();
    start_color();
    init_pair(1, COLOR_GREEN, COLOR_BLACK); // 已完成部分:绿色前景,黑色背景
    init_pair(2, COLOR_RED, COLOR_BLACK); // 剩余部分(虽然用红色可能不太合适,但为演示目的):红色背景

    break();
    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) {
        // 计算已完成的进度和剩余的进度
        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++) {
            mwprintw(win, bar_y, bar_x + i, "#");
        }
        attroff(COLOR_PAIR(1));

        // 剩余部分(用背景色填充)
        attron(A_BOLD | COLOR_PAIR(2)); // 加粗并设置背景色为红色(仅用于演示)
        for (int i = completed; i < max_progress; i++) {
            mwprintw(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; // 居中显示
        mwprintw(win, WINDOW_HEIGHT - 1, percent_x, percent_str);

        wrefresh(win); // 刷新窗口以显示更新

        // 增加进度
        progress += PROGRESS_INCREMENT;

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

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

推荐⼀篇不错的使用指南:https://blog.csdn.net/bdn_nbd/article/details/134019142

五. 目标文件

编译和链接这两个步骤,在Windows下被我们的IDE封装的很完美,我们一般都是一键构建非常方便,但一旦遇到错误的时候呢,尤其是链接相关的错误,很多人就束手无策了。在Linux下,我们之前也学习过如何通过gcc编译器来完成这一系列操作。

接下来我们深入探讨一下编译和链接的整个过程,来更好的理解动态静态库的使用原理。

先来回顾下什么是编译呢?编译的过程其实就是将我们程序的源代码翻译成CPU能够直接运行的机器代码。

比如:在一个源文件 hello.c 里便简单输出"hello world!",并且调用一个run函数,而这个函数被定义在另一个源文件 code.c 中。这里我们就可以调用 gcc -c 来分别编译这两个源文件。

c 复制代码
// hello.c
#include <stdio.h>

void run();
int main() {
    printf("hello world!\n");
    run();
    return 0;
}
// code.c
#include<stdio.h>
void run {
printf("running... "n");
h
编译两个源文件
$ gcc -c hello.c
$ gcc -C code.c
$ ls
code.c code.0 hello.c hello.0

可以看到,在编译之后会生成两个扩展名为.o的文件,它们被称作目标文件。要注意的是如果我们修改了一个原文件,那么只需要单独编译它这一个,而不需要浪费时间重新编译整个工程。目标文件是一个二进制的文件,文件的格式是ELF,是对二进制代码的一种封装。

bash 复制代码
$ file hello.o
hello.o: ELF 64-bit LSB relocatable, x86-64, version 1 (SYSV), not stripped
## file命令⽤于辨识⽂件类型

后言

这就是深入理解动静态库本质及其制作。大家自己好好消化!今天就分享到这! 感谢各位的耐心垂阅!咱们下期见!拜拜~

相关推荐
努力学习的小廉36 分钟前
深入了解linux系统—— 库的链接和加载
linux·运维·服务器
Albert_Lsk1 小时前
技术文档写作大纲
java·linux·服务器·技术文档
MoFe11 小时前
【.net core】SkiaSharp 如何在Linux上实现
linux·服务器·.netcore
chbmvdd1 小时前
浅谈学习(费曼学习法)
笔记
风暴智能1 小时前
在ROS2(humble)+Gazebo+rqt下,实时显示仿真无人机的相机图像
linux·无人机
小诸葛的博客2 小时前
Flannel 支持的后端
linux
xiaohanbao092 小时前
day40 python图像数据与显存
python·深度学习·学习·算法·机器学习·图像
似是燕归来2 小时前
STM32 HAL库函数学习 GPIO篇
stm32·单片机·学习
linux-hzh2 小时前
Shell 脚本
linux·shell
2501_911121233 小时前
LVS+Keepalived 高可用
java·linux·运维