当前位置: 首页 > news >正文

Linux中动静态库的制作

1.什么是库

库是写好的现有的,成熟的,可以复⽤的代码。现实中每个程序都要依赖很多基础的底层库,不可能每个⼈的代码都从零开始,因此库的存在意义非同寻常。
本质上来说库是⼀种可执⾏代码的⼆进制形式,可以被操作系统载⼊内存执⾏。库有两种:
  • 静态库: .a[Linux]   .lib[windows]
  • 动态库:.so[Linux]  .dll[windows]

在Ubuntu中的C语言,C++动静态库如下:

// 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
-rw-r--r-- 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-linuxgnu/9/libstdc++.so -> ../../../x86_64-linux-gnu/libstdc++.so.6
$ ls /usr/lib/gcc/x86_64-linux-gnu/9/libstdc++.a
/usr/lib/gcc/x86_64-linux-gnu/9/libstdc++.a

我准备了四份文件:my_stdio.c  my_stdio.h my_string.c my_string.h为后面的章节做准备,源码如下:

// my_stdio.h
#pragma once

#define SIZE 1024
#define FLUSH_NONE 0
#define FLUSH_LINE 1
#define FLUSH_FULL 2

struct IO_FILE
{
    int flag; // 刷新⽅式
    int fileno; // ⽂件描述符
    char outbuffer[SIZE];
    int cap;
    int size;
};

typedef struct IO_FILE mFILE;

mFILE *mfopen(const char *filename, const char *mode);
int mfwrite(const void *ptr, int num, mFILE *stream);
void mfflush(mFILE *stream);
void mfclose(mFILE *stream);

// my_stdio.c
#include "my_stdio.h"
#include <string.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>

mFILE *mfopen(const char *filename, const char *mode)
{
    int fd = -1;
    if(strcmp(mode, "r") == 0)
    {
        fd = open(filename, O_RDONLY);
    }
    else if(strcmp(mode, "w")== 0)
    {
        fd = open(filename, O_CREAT|O_WRONLY|O_TRUNC, 0666);
    }
    else if(strcmp(mode, "a") == 0)
    {
        fd = open(filename, O_CREAT|O_WRONLY|O_APPEND, 0666);
    }

    if(fd < 0) return NULL;

    mFILE *mf = (mFILE*)malloc(sizeof(mFILE));
    if(!mf)
    {
        close(fd);
        return NULL;
    }

    mf->fileno = fd;
    mf->flag = FLUSH_LINE;
    mf->size = 0;
    mf->cap = SIZE;

    return mf;
}

void mfflush(mFILE *stream)
{
    if(stream->size > 0)
    {
        // 写到内核⽂件的⽂件缓冲区中!
        write(stream->fileno, stream->outbuffer, stream->size);

        // 刷新到外设
        fsync(stream->fileno);
        stream->size = 0;
    }
}

int mfwrite(const void *ptr, int num, mFILE *stream)
{
    // 1. 拷⻉
    memcpy(stream->outbuffer+stream->size, ptr, num);
    stream->size += num;
    // 2. 检测是否要刷新
    if(stream->flag == FLUSH_LINE && stream->size > 0 && stream->outbuffer[stream->size-1]== '\n')
    {
        mfflush(stream);
    }
    return num;
}

void mfclose(mFILE *stream)
{
    if(stream->size > 0)
    {
        mfflush(stream);
    }

    close(stream->fileno);
}

// my_string.h
#pragma once

int my_strlen(const char *s);

// my_string.c
#include "my_string.h"
int my_strlen(const char *s)
{
    const char *end = s;
    while(*end != '\0')end++;

    return end - s;
}

 2. 静态库

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

2.1. 静态库的生成

// Makefile

libmystdio.a:my_stdio.o my_string.o
    @ar -rc $@ $^
    @echo "build $^ to $@ ... done"

%.o:%.c
    @gcc -c $<
    @echo "compling $< 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"

步骤1:先将.c文件编译成.o文件

步骤2:将.o文件进行打包形成.a文件

步骤3:将.a文件和.h文件进行打包

步骤4:在需要使用时可以将静态库进行压缩解包使用。

2.2. 静态库的使用

 

// 任意⽬录下,新建
// main.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));

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

    mfwrite(s, my_strlen(s), fp);
    mfwrite(s, my_strlen(s), fp);
    mfwrite(s, my_strlen(s), fp);

    mfclose(fp);
    return 0;
}

使用场景1:头⽂件和库⽂件安装到系统路径下

$ gcc main.c -lmystdio

使用场景2:头⽂件和库⽂件和我们⾃⼰的源⽂件在同⼀个路径下

$ gcc main.c -L. -lmymath

使用场景3:头⽂件和库⽂件有⾃⼰的独⽴路径

gcc main.c -I头⽂件路径 -L库⽂件路径 -lmymath
  • -L: 指定库路径。
  • -I: 指定头⽂件搜索路径。
  • -l: 指定库名。
  • 测试⽬标⽂件⽣成后,静态库删掉,程序照样可以运⾏。
  • -static选项。
  • 库⽂件名称和引⼊库的名称:去掉前缀 lib ,去掉后缀 .so , .a ,如: libc.so -> c

3.动态库

  • 动态库(.so):程序在运⾏的时候才去链接动态库的代码,多个程序共享使⽤库的代码。

  • ⼀个与动态库链接的可执⾏⽂件仅仅包含它⽤到的函数⼊⼝地址的⼀个表,⽽不是外部函数所在⽬标⽂件的整个机器码。
  • 在可执⾏⽂件开始运⾏以前,外部函数的机器码由操作系统从磁盘上的该动态库中复制到内存中,这个过程称为动态链接(dynamic linking)
  • 动态库可以在多个程序间共享,所以动态链接使得可执⾏⽂件更⼩,节省了磁盘空间。操作系统采⽤虚拟内存机制允许物理内存中的⼀份动态库被要⽤到该库的所有进程共⽤,节省了内存和磁盘空间。

3.1. 动态库的生成 

// Makefile
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
// 场景1:头⽂件和库⽂件安装到系统路径下
$ gcc main.c -lmystdio

// 场景2:头⽂件和库⽂件和我们⾃⼰的源⽂件在同⼀个路径下
$ gcc main.c -L. -lmymath // 从左到右搜索-L指定的⽬录

// 场景3:头⽂件和库⽂件有⾃⼰的独⽴路径
$ gcc main.c -I头⽂件路径 -L库⽂件路径 -lmymath

$ ldd libmystdio.so // 查看库或者可执⾏程序的依赖
linux-vdso.so.1 => (0x00007fffacbbf000)
libc.so.6 => /lib64/libc.so.6 (0x00007f8917335000)
/lib64/ld-linux-x86-64.so.2 (0x00007f8917905000)

// 以场景2为例
$ 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
wzh@hcss-ecs-c6ff:$ ./a.out

3.3. 库运行搜索路径

3.3.1. 问题

在Linux系统中,动态链接库的搜索路径是如何配置的。通常,当程序运行时需要加载共享库(.so文件),系统会按照一定的顺序在特定的路径下查找这些库文件。如果找不到对应的库,程序会报错,比如“error while loading shared libraries”。

3.3.2. 解决方案

  • 拷⻉ .so ⽂件到系统共享库路径下, ⼀般指 /usr/lib /usr/local/lib /lib64 等路径。
  • 向系统共享库路径下建⽴同名软连接。
  • 更改环境变量: LD_LIBRARY_PATH
  • ldconfig⽅案:配置/ etc/ld.so.conf.d/ ,ldconfig更新。 

4. 目标文件

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

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

 

http://www.dtcms.com/a/107111.html

相关文章:

  • AF3 OpenFoldSingleMultimerDataset类解读
  • 产品经理的大语言模型课 04 -模型应用的云、边、端模式对比
  • STM32开发板上生成PWM正弦波
  • XT1861 同步升压 DC-DC 变换器
  • Spring Boot 3.4.3 基于 JSqlParser 和 MyBatis 实现自定义数据权限
  • 【Qt】QList<T> list(n)构造函数创建列表时元素 T的默认值
  • AI写程序:视频裁剪小工具
  • 【模板】P2764 最小路径覆盖问题
  • 【Linux】ELF文件与库的加载
  • RNN模型与NLP应用——(8/9)Attention(注意力机制)
  • LeetCode每日温度
  • Zemax设计实例:手机广角镜头设计(FOV 120°)
  • 在centos7上安装ragflow
  • 第149场双周赛:找到字符串中合法的相邻数字、重新安排会议得到最多空余时间 Ⅰ、
  • 腾讯云智测试开发面经
  • javaSE————网络原理
  • 从吉卜力漫画到艺术创造:GPT-4o多种风格绘图Prompt大全
  • Redisson 操作 Redis Stream 消息队列详解及实战案例
  • HttpClient-03.入门案例-发送POST方式请求
  • Dell G16 7620克隆硬盘 扩容
  • 移远RG200U-CN模组适配问题
  • OpenCV 图形API(7)用于将笛卡尔坐标(x, y)转换为极坐标(magnitude, angle)函数cartToPolar()
  • 【编程之路】按指定大小合并数据块
  • 局域网内便捷实现多设备文件共享方法
  • 【论文阅读】Anchor Graph Network for Incomplete Multiview Clustering
  • 【django】3 (django路由) 路由配置和反向解析
  • Python•输入输出基本运算
  • 浏览器指纹攻防技术深度解析:从多账号隔离到自动化矩阵架构设计
  • UG NX二次开发(C#)-采用Open/C与NXOpen获取曲线的长度
  • 【dp】有序三元组中的最大值 I