【Linux】动静态库的制作与使用

1、库

1.1 前置条件

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

使用ldd命令可以查看可执行程序运行时依赖哪些动态库

通过file命令可以查看程序是静态链接还是动态链接

默认是动态链接

在Linux中:

  • 静态库:.a ,命名规范libxxx.a
  • 动态库:.so ,命名规范libxxx.so

gcc和g++默认是动态链接,加-static 强制静态链接

1.2 库的定义

.c源文件经过预处理,再经过编译和汇编行程.o目标文件,再经过链接,最后形成可执行程序

当编译生成.o时,只看函数/变量声明,不看实现,和其他.o文件无关

库的本质是一堆.o文件打包成一个文件,无论是静态库还是动态库,其核心都是由源文件(.c)编译生成的目标文件(.o)组成

静态库:用ar归档工具把很多.o文件打包

动态库:gcc把一堆.o文件打包生成共享库

对外给别人使用库,必须交付两样东西:

  1. 库文件:存放函数二进制实现
  2. 头文件.h:只有函数声明,相当于使用说明书,库中不能有main函数

2、静态库

程序在编译链接时把库的代码链接到可执行文件中,程序运行时将不再需要静态库

2.1 静态库的制作

calc.h:

cpp 复制代码
#pragma once
int add(int a,int b);
int sub(int a,int b);

add.c:

cpp 复制代码
#include "calc.h"
int add(int a,int b)
{
    return a+b;
}

sub.c:

cpp 复制代码
#include "calc.h"
int sub(int a,int b)
{
    return a-b;
}

main.c:

cpp 复制代码
#include <stdio.h>
#include "calc.h"
int main()
{
    printf("10+3=%d\n", add(10,3));
    printf("10-3=%d\n", sub(10,3));
    return 0;
}

2.2 制作动态库

Makefile:

makefile 复制代码
libmystdio.a:add.o sub.o
  ar -rc $@ $^
%.o:%.c
  gcc -c $<
  
.PHONY:clean
clean:
	rm -rf *.o *.a
  1. 把.c文件编译成目标文件.o
  2. 使用ar归档工具打包成静态库libxxx.a

静态库的生成:

2.3 静态库的发布

发布库:

makefile 复制代码
libmystdio.a: add.o sub.o
	ar -rc $@ $^

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

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

// 发布库
.PHONY: output
output: libmystdio.a
	mkdir -p mylib
	mkdir -p mylib/include
	mkdir -p mylib/lib
	cp *.h mylib/include
	cp *.a mylib/lib

发布库本质是把目录拷贝给别人

2.4 静态库的使用和安装

编译链接相关gcc选项

-I[头文件路径] 指令指定头文件搜索路径,-L[库文件路径] 指定库文件搜索路径,-I[库名] 指定要链接的库名称

  1. 本地使用

main.c:

cpp 复制代码
#include <stdio.h>
#include "calc.h"

int main()
{
    int a = add(10,20);
    int b = sub(10,20);
    printf("add=%d\n", a);
    printf("sub=%d\n", b);
    return 0;
}

-I./mylib/include :找 calc.h 头文件

-L./mylib/lib :找 libmystdio.a 静态库

-lmystdio :链接这个库

  1. 安装到系统全局
bash 复制代码
#安装
sudo cp mylib/include/calc.h /usr/include/
sudo cp mylib/lib/libmystdio.a /usr/lib64/

安装完,不用写-I-L,但是‑l不能省略

bash 复制代码
gcc main.c -lmystdio -o main

卸载库:

bash 复制代码
sudo rm /usr/include/calc.h
sudo rm /usr/lib64/libmystdio.a

软连接安装:

bash 复制代码
sudo ln -s /home/me/lesson14/mylib/lib/libmystdio.a  /usr/lib64/libmystdio.a
sudo cp mylib/include/calc.h /usr/include/

3、动态库

动态库:运行时才把库加载到内存;多个程序共用内存里面同一份库代码,节省内存,编译链接阶段只记录依赖,不会把库代码拷贝进可执行程序

3.1 动态库的制作

mystdio.c:

cpp 复制代码
#include "mystdio.h"
#include <string.h> // string.h 你也可以封装
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>

#define TRY_FLUSH 1
#define MUST_FLUSH 2

myFILE *myfopen(const char *pathname, const char *mode) // r, w, a, r+, w+..
{
    int fd = -1;
    int flags = 0;
    if(strcmp(mode, "r") == 0)
    {
        flags = O_RDONLY;
        fd = open(pathname, flags);
    }
    else if(strcmp(mode, "w") == 0)
    {
        flags = O_WRONLY | O_CREAT | O_TRUNC;
        fd = open(pathname, flags, MODE);
    }
    else if(strcmp(mode, "a") == 0)
    {
        flags = O_WRONLY | O_CREAT | O_APPEND;
        fd = open(pathname, flags, MODE);
    }
    else
    {
        // TODO
    }
    if(fd < 0)
        return NULL;

    myFILE *fp = (myFILE *)malloc(sizeof(myFILE));
    if(fp == NULL)
        return NULL;

    fp->fd = fd;
    fp->flags = flags;
    fp->flush_mode = LINE_BUFFER;
    fp->cap = SIZE;
    fp->pos = 0;

    return fp;
}

static void myfflushcore(myFILE *fp, int flag)
{
    if(fp->pos == 0)
        return;
    if((fp->flush_mode & LINE_BUFFER) || (flag & MUST_FLUSH))
    {
        // "abcd\n"
        if((fp->outbuffer[fp->pos-1] == '\n') || (flag & MUST_FLUSH))
        {
            // 写到内核中
            write(fp->fd, fp->outbuffer, fp->pos);
            fp->pos = 0; // 清空缓冲区
        }
    }
    else if(fp->flush_mode & FULL_BUFFER)
    {
        //if(fp->pos == fp->cap)
    }
    else if(fp->flush_mode & NON_BUFFER)
    {
        // write();
    }
}

void myfflush(myFILE *fp)
{
    myfflushcore(fp, MUST_FLUSH);
}

int myfputs(const char *str, myFILE *fp)
{
    if(strlen(str) == 0)
        return 0;
    // step 1:向文件流里面写,本质是: 文件缓冲区 -> 拷贝
    memcpy(fp->outbuffer + fp->pos, str, strlen(str));
    fp->pos += strlen(str);

    // step 2: 如果条件允许,可以自己刷新
    myfflushcore(fp, TRY_FLUSH);
    return strlen(str);
}

void myfclose(myFILE *fp)
{
    // 1. 强制刷新到内核
    myfflush(fp);

    // 1.2 强制刷新到磁盘
    fsync(fp->fd); // 不是必选的

    // 2. 关闭文件
    close(fp->fd);

    // 3. free
    free(fp);
}

mystdio.h:

cpp 复制代码
#pragma once

#include <stdio.h>

#define SIZE 1024
#define NON_BUFFER  1 // 1
#define LINE_BUFFER 2 // 10
#define FULL_BUFFER 4 // 000 ... 000 100

#define MODE 0666

typedef struct  _myFILE
{
    int fd;
    int flags;
    int flush_mode;
    char outbuffer[SIZE];
    int pos;
    int cap;
}myFILE;

myFILE *myfopen(const char *pathname, const char *mode); // r, w, a, r+, w+..
int myfputs(const char *str, myFILE *fp);
void myfflush(myFILE *fp);
void myfclose(myFILE *fp);

mystring.c:

cpp 复制代码
#include "mystring.h"
#include <stdio.h>

void mystrlen()
{
    printf("我自己的string len 函数!\n");
}

mystring.h:

cpp 复制代码
#pragma  once

void mystrlen();

3.2 动态库的生成

Makefile:

makefile 复制代码
libmystdio.so:mystdio.o mystring.o
	gcc -shared -o $@ $^
%.o:%.c
	gcc -fPIC -c $<

.PHONY:clean
clean:
	rm -rf *.o *.so mylib

相比较于静态库,动态库的Makefile需要加上-fPIC 位置无关代码和-shared-shared 是告诉 gcc 输出文件类型:生成动态库 .so,而不是普通可执行程序

libmystdio.so为生成的动态库

形成动态库不用ar,而是继续使用gcc、g++,说明使用动态库是最常见的场景

3.3 动态库的发布

makefile 复制代码
.PHONY:output
output:
	mkdir -p mylib
	mkdir -p mylib/include
	mkdir -p mylib/lib
	cp *.h mylib/include
	cp *.so mylib/lib

3.4 动态库的使用

cpp 复制代码
#include "mystdio.h"
#include "mystring.h"

int main()
{
    myFILE *fp = myfopen("log.txt", "a"); // r, w, a, r+, w+..
    mystrlen();

    return 0;
}

这里静态库的使用和动态库的使用没有区别,可以生成可执行文件

但在运行时会报错

使用ldd命令查看时,libmystdio.so后面指向not found,在gcc编译时已经使用-I和-L告诉编译器动态库的路径和名字了,为什么找不到?因为可执行程序运行靠加载器,上面的not found表示加载器不知道动态库在哪里

为什么使用静态库时没有这个问题?因为静态库中的方法拷贝到了程序内部,程序运行就不再需要库了

第一种解决方案:直接把对应的库拷贝到系统的lib64下

第二种方案:在系统默认路径下建立软链接

第三种:将自己库所在的路径添加到系统环境变量LD_LIBRARY_PATH中

第四种,如果我们想让库在程序运行时自动被找到,那么可以在/etc/ld.so.conf.d 路径下添加一个.conf结尾的配置文件,再执行ldconfig指令,将所有配置文件重新加载

相关推荐
艾伦_耶格宇2 小时前
【ELK】-4 logstash的搭建及操作
linux·运维·ubuntu·elk
mifengxing2 小时前
文件逻辑结构、物理结构与磁盘空闲存储空间管理
大数据·linux·运维·操作系统·计算机408
小此方4 小时前
Re:Linux系统篇(五十七)线程篇 · 十:基于环形缓冲的生产者消费者模型与信号量
linux·运维·驱动开发
fanged5 小时前
Linux的DD命令
linux·运维·服务器
张洛闻Eren6 小时前
MySQL 管理复制拓扑【MySQL第四课】
linux·运维·数据库·mysql
xiaohua10096 小时前
Linux-JVM线程查询
linux·jvm
blueSatchel7 小时前
u-boot启动过程(bootROM到SPL到u-boot)
linux·u-boot
祖力557 小时前
快速学会数据结构中的链式队列与循环队列
linux·c语言·数据结构·算法