核心内容摘要
PH黑黄:一抹神秘色彩的魅力探索
✨道路是曲折的前途是光明的 专注C/C、Linux编程与人工智能领域分享学习笔记 感谢各位小伙伴的长期陪伴与支持欢迎文末添加好友一起交流
基础知识
1 铺垫
静态库的制作
静态库的使用
四、
总结
基础知识一些基础的动静态库知识在下面文章中会讲解到可以查阅《Linux编译器gcc/g食用指南》-CSDN博客
1 铺垫我们日常用 C 语言标准库时只需要#include stdio.h就能调用printf但从来不用关心printf是怎么实现的 —— 这就是库的核心设计逻辑头文件负责 “说明怎么用”库文件负责 “藏住怎么实现”。
头文件.h的本质是一份 “使用说明书”里面只声明函数名、参数类型、返回值、外部变量比如extern int errno;告诉编译器 “有这个函数 / 变量你可以这么调用”但不包含任何实际的代码逻辑。
而真正的实现代码会被编译成二进制的目标文件.o再打包成库文件静态库.a/ 动态库.so既避免了源码泄露也让用户不用关心底层实现只需要按头文件的说明调用即可。
如果要把我们的库给别人使用我们需要提供对应的头文件和方法这个方法就是我们的xxx.o文件。
然后用户在自己的mian函数里面使用包含头文件的方法。
比如系统把printf的实现编译成二进制库放在 VS / 编译器的默认路径下我们只需要包含stdio.h拿到调用说明编译器就会自动找到对应的库文件把printf的实现链接到我们的程序里。
所以接下来我们来看看如何自己制作一个静态库并且使用它。
静态库的制作我们再此处就实现一个简单的静态库提供加法运算方法。
那我们就需要先要明白静态库制作的两个基础文件它们分别是xxx.oxxx.h文件我们这里使用add.c和add.h。
//add.h#progmaonce//防止头文件重复包含#includstdio.hexternintmyerrno;//声明错误码intadd(intx,inty);为了能看出错误码起到了作用我们只允许我们的数字0。
#includeadd.hintmyerrno0;intadd(intx,inty){if(x0||y
{myerrno1;// 标记错误return0;}myerrno0;// 无错误returnxy;}此时我们就要把我们的源文件.c通过汇编形成我们的目标文件.o。
然后就把我们的目标文件打包成库这里就许要使用到我们的归档工具ar它是专门用于我们静态库的打包。
静态库需要以lin开头.a结尾这里我们统一使用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.omylib删除交付目录包含拷贝的头文件/库文件.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/includecp -f *.a mylib/addlib拷贝命令同上把当前目录所有.a静态库文件拷贝到mylib/addlib我们执行make指令就会现形成目标文件add.o然后让目标文件打包成库libadd.a然后我们在执行make output就可以把所有的.h文件放在mylib目录下的include文件里同样如此我们的库也放在mylib目录下的addlib里如果路径不存在就直接创建。
里面的mylib就是我们需要提供给用户的静态库里面包含了头文件和方法。
注意因为output目标依赖$(lib)也就是libadd.a所以即使我们跳过make先直接执行make outputMakefile 也会自动先执行make的逻辑生成add.o→打包libadd.a再执行拷贝操作不会出现 “拷贝空库文件” 的问题。
静态库的使用我们此时站在用户的角度使用我们制作的静态库首先要准备好自己的测试文件test.c下面再写然后把我们提供的静态库目录mylib移动到测试目录下以此模拟用户下载并存放库文件的操作。
#includeadd.hintmain(){inta10;intb20;intcadd(a,b);printf(a b %d\n,c);return0;}此时我们想要运行test.c的话就要先使用gcc编译形成可执行程序文件。
看到如此多的错误是不是慌了别慌别慌因为我们的gcc test.c是仅编译当前目录源码的极简指令会导致下面的结果。
在当前目录找add.h但你的add.h在./mylib/include里找不到不链接任何静态库libadd.a在./mylib/addlib里链接器根本不知道因为找不到add.h所以add函数和printfadd.h里的stdio.h都成了 “隐式声明”最终链接时找不到add的实现直接报错。
该如何解决这种情况呢其实有两种解决方法其一便是使用-I-L-l指令。
-I指定头文件搜索路径。
-L指定库文件搜索路径。
-l指明需要链接库文件路径下的哪一个库。
此时我们直接运行./a.out即可第二种办法就是直接把我们的头文件和库文件拷贝到系统路径下echo | gcc -E -Wp,-v -查找系统默认头文件路径ld --verbose | grep SEARCH_DIR | tr -s ; \n查看系统默认库文件路径我们直接拷贝sudo cp add.h /usr/local/includesudo cp libadd.a /usr/local/lib明明已经拷贝到系统路径下了为什么还会有错呢gcc test.c只会编译源码但不会自动链接任何自定义库哪怕库文件在系统路径里。
你必须显式加-ladd链接器才会去系统库路径/usr/local/lib找libadd.a。
如果想要删除你拷贝到系统路径下对应的库我们只需要sudo rm 路径就可以了切记切记路径不要输错说明我们已经删除成功。
其实还有一种不需要把第三方库和头文件拷贝到系统路径下的方法那就是软链接通过软链接的方式将第三方库以链接的形式安装到系统的特定的路径下。
至于软链接的方法我们前面有讲
四、
总结通过本次学习我们明确在使用第三方库如自定义静态库时gcc编译指令中的-l库名参数是必须添加的——它的作用是告诉链接器需要链接指定名称的库无论库文件放在自定义路径还是系统默认路径这一步都无法省略。
此外链接行为与库的类型/数量相关若系统中仅存在静态库如libadd.agcc会默认链接该静态库若需要链接多个库无论静态库还是动态库只需依次添加多个-l库名参数如-ladd -lmathgcc即可完成多库的链接操作。