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文件打包生成共享库
对外给别人使用库,必须交付两样东西:
- 库文件:存放函数二进制实现
- 头文件.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

- 把.c文件编译成目标文件.o
- 使用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[库名] 指定要链接的库名称
- 本地使用
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 :链接这个库
- 安装到系统全局
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指令,将所有配置文件重新加载