【Linux】库制作与原理(一):静态库的制作与使用


✨道路是曲折的,前途是光明的!

📝 专注C/C++、Linux编程与人工智能领域,分享学习笔记!

🌟 感谢各位小伙伴的长期陪伴与支持,欢迎文末添加好友一起交流!


一、基础知识

一些基础的动静态库知识在下面文章中会讲解到,可以查阅《Linux编译器:gcc/g++食用指南》-CSDN博客

1.1 铺垫

  1. 我们日常用 C 语言标准库时,只需要#include <stdio.h>就能调用printf,但从来不用关心printf是怎么实现的 ------ 这就是库的核心设计逻辑:头文件负责 "说明怎么用",库文件负责 "藏住怎么实现"

  2. 头文件(.h)的本质是一份 "使用说明书":里面只声明函数名、参数类型、返回值、外部变量(比如extern int errno;),告诉编译器 "有这个函数 / 变量,你可以这么调用",但不包含任何实际的代码逻辑。

  3. 而真正的实现代码,会被编译成二进制的目标文件(.o),再打包成库文件(静态库.a/ 动态库.so),既避免了源码泄露,也让用户不用关心底层实现,只需要按头文件的说明调用即可。

  4. 如果要把我们的库给别人使用,我们需要提供对应的头文件和方法,这个方法就是我们的xxx.o文件。然后用户在自己的mian函数里面使用包含头文件的方法。

比如系统把printf的实现编译成二进制库,放在 VS / 编译器的默认路径下,我们只需要包含stdio.h(拿到调用说明),编译器就会自动找到对应的库文件,把printf的实现链接到我们的程序里。

所以接下来我们来看看如何自己制作一个静态库并且使用它。


二、静态库的制作

我们再此处就实现一个简单的静态库提供加法运算方法。那我们就需要先要明白静态库制作的两个基础文件,它们分别是xxx.o``xxx.h文件,我们这里使用add.cadd.h

c 复制代码
//add.h
#progma once //防止头文件重复包含
#includ <stdio.h>

extern int myerrno; //声明错误码

int add(int x, int y);
  • 为了能看出错误码起到了作用,我们只允许我们的数字>=0。
c 复制代码
#include "add.h"

int myerrno = 0;

int add(int x, int y) {
    if (x < 0 || y < 0) {
        myerrno = 1; // 标记错误
        return 0;
    }
    myerrno = 0; // 无错误
    return x + y;
}
  • 此时我们就要把我们的源文件.c通过汇编形成我们的目标文件.o
  • 然后就把我们的目标文件打包成库,这里就许要使用到我们的归档工具ar,它是专门用于我们静态库的打包。
  • 静态库需要以lin开头,.a结尾

这里我们统一使用Makefile自动化构建工具,方便我们的后续操作

makefile 复制代码
lib = libadd.a

$(lib):add.o
	ar -rc $@ $^

add.o:add.c
	gcc -c $^ -o $@

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

.PHONY:output
output:$(lib)
	mkdir -p mylib/include
	mkdir -p mylib/addlib
	cp -f *.h mylib/include
	cp -f *.a mylib/addlib
代码行 指令/语法 详细解释
lib = libadd.a 变量定义 定义名为lib的变量,值为libadd.a(要生成的静态库名称),静态库命名规范是lib开头、.a结尾,后续链接时用-ladd即可引用
$(lib):add.o 构建规则 定义静态库libadd.a的依赖关系:生成libadd.a必须先有add.o目标文件,只有add.o更新/不存在时,才会执行下方打包命令
ar -rc $@ $^ 打包命令 ar是GNU归档工具(打包静态库用); -r:替换/添加文件到静态库;-c:静态库不存在时创建; $@:自动变量,代表当前目标(libadd.a); $^:自动变量,代表所有依赖(add.o); 整行:把add.o打包到libadd.a(不存在则创建,存在则替换)
add.o:add.c 构建规则 定义add.o目标文件的依赖关系:生成add.o必须先有add.c源文件,只有add.c更新/不存在时,才会执行下方编译命令
gcc -c $^ -o $@ 编译命令 gcc是C编译器; -c:只编译不链接,仅生成目标文件(.o); $^:代表依赖文件(add.c); -o $@:指定输出文件为$@add.o); 整行:编译add.c生成add.o目标文件
.PHONY:clean 伪目标声明 声明clean是"伪目标",避免当前目录有同名clean文件时,make clean规则失效;伪目标会强制执行对应命令,不检查文件状态
clean: 目标定义 定义clean目标,用于清理构建生成的文件
rm -rf *.a *.o mylib 清理命令 rm是删除命令; -r:递归删除(用于删目录);-f:强制删除(无提示); *.a:删除所有.a后缀文件(即libadd.a); *.o:删除所有.o后缀文件(即add.o); mylib:删除交付目录(包含拷贝的头文件/库文件)
.PHONY:output 伪目标声明 声明output是"伪目标",避免当前目录有同名output文件时规则失效
output:$(lib) 构建规则 定义output目标的依赖关系:执行output前必须先生成$(lib)libadd.a),确保先有静态库再拷贝文件
mkdir -p mylib/include 目录创建 mkdir是创建目录命令; -p:目录不存在时创建,存在时不报错; 整行:创建mylib/include目录(存放头文件)
mkdir -p mylib/addlib 目录创建 同上,创建mylib/addlib目录(存放静态库文件)
cp -f *.h mylib/include 拷贝命令 cp是拷贝命令; -f:强制覆盖目标目录已有同名文件; 整行:把当前目录所有.h头文件拷贝到mylib/include
cp -f *.a mylib/addlib 拷贝命令 同上,把当前目录所有.a静态库文件拷贝到mylib/addlib
  1. 我们执行make指令,就会现形成目标文件add.o,然后让目标文件打包成库libadd.a
  1. 然后我们在执行make output就可以把所有的.h文件放在mylib目录下的include文件里;同样如此,我们的库也放在mylib目录下的addlib里,如果路径不存在就直接创建。
  1. 里面的mylib就是我们需要提供给用户的静态库,里面包含了头文件和方法。

注意: 因为output目标依赖$(lib)(也就是libadd.a),所以即使我们跳过make先直接执行 make output,Makefile 也会自动先执行make的逻辑(生成add.o→打包libadd.a),再执行拷贝操作,不会出现 "拷贝空库文件" 的问题。


三、静态库的使用

我们此时站在用户的角度使用我们制作的静态库,首先要准备好自己的测试文件test.c(下面再写),然后把我们提供的静态库目录(mylib)移动到测试目录下(以此模拟用户下载并存放库文件的操作)。

c 复制代码
#include "add.h"

int main()
{
    int a = 10;
    int b = 20;
    int c = add(a,b);
    printf("a + b = %d\n",c);

    return 0;
}
  1. 此时我们想要运行test.c的话就要先使用gcc编译形成可执行程序文件。
  1. 看到如此多的错误是不是慌了?别慌别慌,因为我们的gcc test.c仅编译当前目录源码的极简指令,会导致下面的结果。
  • 当前目录add.h(但你的add.h./mylib/include里,找不到);
  • 不链接任何静态库(libadd.a./mylib/addlib里,链接器根本不知道);
  • 因为找不到add.h,所以add函数和printfadd.h里的<stdio.h>)都成了 "隐式声明",最终链接时找不到add的实现,直接报错。
  1. 该如何解决这种情况呢,其实有两种解决方法,其一便是使用-I -L -l指令。
  • -I:指定头文件搜索路径。
  • -L:指定库文件搜索路径。
  • -l:指明需要链接库文件路径下的哪一个库。

此时我们直接运行./a.out即可

  1. 第二种办法就是直接把我们的头文件和库文件拷贝到系统路径下
  • echo | gcc -E -Wp,-v -查找系统默认头文件路径
  • ld --verbose | grep SEARCH_DIR | tr -s ' ;' '\n'查看系统默认库文件路径

我们直接拷贝

  • sudo cp add.h /usr/local/include
  • sudo cp libadd.a /usr/local/lib
  • 明明已经拷贝到系统路径下了为什么还会有错呢?
  • gcc test.c 只会编译源码 ,但不会自动链接任何自定义库(哪怕库文件在系统路径里)。你必须显式加-ladd,链接器才会去系统库路径(/usr/local/lib)找libadd.a

如果想要删除你拷贝到系统路径下对应的库,我们只需要sudo rm 路径就可以了,切记切记!!路径不要输错!

说明我们已经删除成功。

其实还有一种不需要把第三方库和头文件拷贝到系统路径下的方法,那就是软链接,通过软链接的方式将第三方库以链接的形式安装到系统的特定的路径下。至于软链接的方法,我们前面有讲!


四、总结

  • 通过本次学习我们明确:在使用第三方库(如自定义静态库)时,gcc 编译指令中的 -l<库名> 参数是必须添加的------它的作用是告诉链接器需要链接指定名称的库,无论库文件放在自定义路径还是系统默认路径,这一步都无法省略。
  • 此外,链接行为与库的类型/数量相关:若系统中仅存在静态库(如libadd.a),gcc 会默认链接该静态库;若需要链接多个库(无论静态库还是动态库),只需依次添加多个 -l<库名> 参数(如-ladd -lmath),gcc 即可完成多库的链接操作。
相关推荐
皓月盈江2 小时前
Linux Debian13安装virtualbox-7.2_7.2.6-172322-Debian-trixie虚拟机平台无法运行的解决方法
linux·debian·虚拟机·virtualbox·debian13·virtualbox7.2.6·kernel driver
江湖有缘3 小时前
基于华为openEuler部署WikiDocs文档管理系统
linux·华为
野犬寒鸦3 小时前
从零起步学习并发编程 || 第四章:synchronized底层源码级讲解及项目实战应用案例
java·服务器·开发语言·jvm·后端·学习·面试
£漫步 云端彡3 小时前
Golang学习历程【第十一篇 接口(interface)】
开发语言·学习·golang
Web项目开发4 小时前
Dockerfile创建Almalinux9镜像
linux·运维·服务器
jiayong2310 小时前
DevOps体系详解01-核心概念与价值
运维·devops
jiayong2310 小时前
DevOps体系详解02-技术架构与工具链
运维·架构·devops
virus594511 小时前
悟空CRM mybatis-3.5.3-mapper.dtd错误解决方案
java·开发语言·mybatis
pride.li11 小时前
开发板和Linux--nfs服务挂载
linux·运维·服务器