【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指令,将所有配置文件重新加载

相关推荐
2035去旅行12 小时前
bootcmd 详细讲解
linux·嵌入式·arm·soc
陈希12313 小时前
Linux 防火墙配置实战:firewalld、iptables 与云安全组
linux·运维·服务器
女神下凡13 小时前
芯参谋(95):嵌入式文件系统_开发指南
linux·服务器·arm开发·驱动开发·设计规范
且从容.17 小时前
Ubuntu安装flume
linux·ubuntu·flume
承渊政道17 小时前
Linux网络学习【Reactor反应堆模式】
linux·网络·学习·ubuntu·reactor
且从容.20 小时前
ubuntu安装flink
linux·ubuntu·flink
爱和冰阔落1 天前
【Linux网络】TCP 服务器如何并发处理多个客户端?从 fork、僵尸进程到双重 fork
linux·运维·服务器·网络
程序员沉梦听雨1 天前
WSL2的安装与使用
linux·wsl
微小的鱼1 天前
Linux的渲染与显示系统链路
android·java·linux
lisanmengmeng1 天前
alertmanger 标签 rules 打上分组标签
linux·运维·服务器