动态库与静态库的实现
在使用keil的时候遇到这样一个事情,我调用了一个函数,只有函数声明,但是我想查看函数的实现却不行,为什么会这样,这不来了嘛,
我们在使用printf函数等,都是加上头文件直接调用,学校里并没有详细说这个函数的底层实现,以及动态库和静态库的概念
可以理解为我写了一个通用的函数,我只希望别人使用我的函数接口,并不知道我的底层实现,那么我们可以将写的函数放在文件内编译成一个共享库
在C语言开发中,静态库和动态库是两种常见的代码库(Library)类型,它们提供了一种方式来复用代码,允许开发者将常用的函数和资源集中管理。这两种库的主要区别在于它们被链接和加载到程序中的方式。
静态库(Static Libraries)
- 定义:静态库是一种在程序编译链接阶段被整合到最终可执行文件中的库。它通常包含一组预编译的函数、变量和数据结构,这些可以在多个程序中重复使用。
- 文件扩展名:在Unix-like系统中,静态库文件通常有
.a
扩展名(例如,libexample.a
);在Windows系统中,扩展名通常是.lib
。 - 使用方式:当程序被编译时,静态库中相关的代码会被完整地复制到最终的可执行文件中。这意味着,一旦程序被编译,它就不再依赖于原始的库文件。
- 优点
- 可执行文件是自包含的,不需要额外的库文件就可以运行,便于分发。
- 避免了运行时的库版本冲突问题。
- 缺点
- 可能会导致最终的可执行文件体积较大,因为每个程序都包含了一份库的副本。
- 更新库需要重新编译链接所有使用该库的程序。
动态库(Dynamic Libraries)
- 定义:动态库是在程序运行时被加载的库。与静态库不同,动态库不会被复制到可执行文件中,而是在程序执行时从外部文件动态加载。
- 文件扩展名:在Unix-like系统中,动态库文件有
.so
扩展名(例如,libexample.so
);在Windows系统中,扩展名是.dll
。 - 使用方式:程序在运行时由动态链接器(runtime linker/loader)加载动态库。这意味着,多个程序可以共享同一份物理库文件,节省资源。
- 优点
- 节省磁盘空间和内存,因为库的代码在内存中可以被多个程序共享。
- 更新库文件时,不需要重新编译链接使用该库的程序。
- 缺点
- 程序运行时依赖于外部的库文件,如果库文件丢失或版本不兼容,可能导致程序无法运行。
- 可能会遇到“地狱依赖”(Dependency Hell)问题,即管理和解决程序依赖的复杂性。
总的来说,静态库和动态库各有优缺点,选择哪一种类型主要取决于项目的具体需求、分发方式和运行环境。
C语言程序的编译过程可以分为四个主要阶段:预处理(Preprocessing)、编译(Compilation)、汇编(Assembly)和链接(Linking)。在这个过程中,静态库和动态库的引入发生在链接阶段。下面是各个阶段的详细介绍:
1. 预处理(Preprocessing)
- 操作:预处理器处理源代码文件中的预处理指令,如
#include
、#define
、#ifdef
等。这包括展开宏定义,处理条件编译指令,以及包含头文件的内容。 - 结果:预处理后的源代码,此时所有的宏都已展开,所有的头文件内容都已插入到源代码中。
2. 编译(Compilation)
- 操作:编译器将预处理后的源代码转换成汇编语言。在这一阶段,编译器会进行语法分析、语义分析、优化等操作。
- 结果:汇编语言代码,这是一种低级语言,更接近于机器代码但仍然为人类可读。
3. 汇编(Assembly)
- 操作:汇编器将汇编语言代码转换为机器代码,生成目标文件。目标文件包含了程序的机器语言版本,但还没有解决外部引用。
- 结果:目标文件(在Unix-like系统中通常是
.o
或.obj
文件),包含了编译后的代码和数据,但外部符号引用(如函数调用)尚未解析。
4. 链接(Linking)
-
操作
:链接器将一个或多个目标文件与所需的库一起链接,生成最终的可执行文件。在这个阶段,静态库和动态库的引入发生:
- 静态库:链接器会将程序中实际使用到的静态库中的函数和数据复制到最终的可执行文件中。
- 动态库:链接器不会将动态库的代码包含到可执行文件中,而是记录动态库的引用。程序运行时,操作系统负责加载这些动态库。
-
结果:最终的可执行文件。对于静态链接,可执行文件包含了所有必需的库代码;对于动态链接,可执行文件依赖于外部的动态库。
静态库和动态库的引入
- 静态库:在链接阶段,静态库的相关代码被整合进最终的可执行文件中,使得可执行文件在没有静态库的情况下也能独立运行。
- 动态库:在链接阶段,只记录对动态库的引用。程序运行时,动态库被操作系统加载,程序中的调用通过动态链接器重定向到这些库中的实现。
所以在使用动态库时我们会将动态库放入运行的环境中,这是有门槛的
放在哪里?
动态库和操作系统
- 动态链接器:操作系统的一部分,负责运行时解析程序对动态库函数的引用,加载库到内存,以及管理库之间的依赖关系。在Linux和Unix-like系统中,动态链接器通常是
ld-linux.so
,在Windows中是Windows Loader
。 - 环境依赖:因为动态库的这些特性,它们主要用于有完整操作系统支持的环境中,如桌面操作系统、服务器操作系统或者嵌入式操作系统等。
嵌入式系统和动态库
在嵌入式系统中,是否使用动态库取决于嵌入式操作系统的功能和资源限制:
- 有操作系统的嵌入式环境:对于运行Linux、RTOS(实时操作系统)或其他具有动态链接能力的嵌入式操作系统的设备,可以使用动态库。例如,基于Linux的嵌入式系统可以利用动态库来节省内存和存储空间,同时提高代码重用。
- 无操作系统或裸机(Bare-metal)环境:在没有操作系统的嵌入式环境中,通常不使用动态库。这些环境下的程序直接在硬件上运行,没有动态链接器来管理运行时的库加载。在这种情况下,通常会使用静态库或直接将代码编译到可执行文件中。
所以在使用嵌入式设备时候,只有在使用操作系统时,并且含有动态链接器时,才可以使用动态库
下面分别在linux环境与windows环境下制作一个动态库与静态库
linux与windows下 动态库与静态库的后缀
Linux
-
动态库:
.so
(Shared Object)
- 例如,
libexample.so
- 例如,
-
静态库:
.a
(Archive)
- 例如,
libexample.a
- 例如,
在Linux中,动态库通常以.so
为后缀,表示这是一个共享对象文件,可以在运行时被多个程序共享。静态库以.a
为后缀,表示这是一个归档文件,包含了一组被编译过的对象文件,链接器在构建可执行文件时会从中提取必要的对象。
Windows
-
动态库:
.dll
(Dynamic-Link Library)
- 例如,
example.dll
- 例如,
-
静态库:
.lib
- 例如,
example.lib
- 例如,
在Windows中,动态库以.dll
为后缀,表示这是一个动态链接库文件,它包含了可以被多个程序共享的函数和资源。静态库使用.lib
后缀,虽然在某些情况下.lib
文件也可以用于动态链接(作为导入库),但通常.lib
用于静态链接,包含了直接链接到最终可执行文件中的代码和数据。windows下的静态库也可以是.a后缀
下面使用一个简单的示例程序来制作一个静态库与动态库, 写四个文件,函数分别为加减乘除,并在一个共有的头文件中进行声明
linux下静态库的制作
为了创建一个包含加、减、乘、除四个基本数学运算的静态库(我们将其命名为libmathops
),需要编写四个源文件和一个头文件。下面是这些文件的内容和制作静态库的步骤:
步骤 1: 编写源文件和头文件
touch add.c subtract.c multiply.c divide.c mathops.h
add.c
#include "mathops.h"
int add(int a, int b) {
return a + b;
}
subtract.c
#include "mathops.h"
int subtract(int a, int b) {
return a - b;
}
multiply.c
#include "mathops.h"
int multiply(int a, int b) {
return a * b;
}
divide.c
#include "mathops.h"
float divide(int a, int b) {
if (b != 0) return (float)a / b;
return 0.0; // Error case
}
mathops.h
#ifndef MATHOPS_H
#define MATHOPS_H
int add(int a, int b);
int subtract(int a, int b);
int multiply(int a, int b);
float divide(int a, int b);
#endif
以上一共是五个文件,四个函数文件 一个函数声明
步骤 2: 编译源文件
使用GCC编译器编译每个源文件,生成目标文件(.o 文件):
gcc -c add.c
gcc -c subtract.c
gcc -c multiply.c
gcc -c divide.c
这些命令会生成add.o
、subtract.o
、multiply.o
和divide.o
目标文件。
文件过多自然而然就引入makefile了
步骤 3: 创建静态库
使用ar
工具将所有目标文件打包成一个静态库文件:
ar rcs libmathops.a add.o subtract.o multiply.o divide.o
这个命令创建了一个名为libmathops.a
的静态库文件,包含了之前编译的所有目标文件。
步骤 4: 使用静态库
现在可以在其他程序中使用这个静态库了。假设有一个main.c
文件,使用了这个库:
#include <stdio.h>
#include "mathops.h"
int main() {
int sum = add(1, 2);
int difference = subtract(10, 5);
int product = multiply(4, 5);
float quotient = divide(20, 4);
printf("Sum: %d\n", sum);
printf("Difference: %d\n", difference);
printf("Product: %d\n", product);
printf("Quotient: %f\n", quotient);
return 0;
}
编译这个程序并链接静态库:
gcc -o main main.c -L. -lmathops
这里,-L.
指定了库文件搜索路径(当前目录),-lmathops
指定了要链接的库(去掉前缀lib
和后缀.a
)。
完成以上步骤后,就成功创建了一个静态库,并在一个程序中使用了它。
linux下动态库的制作
还是以上的文件
步骤 1: 编译源文件为共享对象
使用GCC编译器编译每个源文件,不过这次我们需要添加-fPIC
选项来生成位置无关代码(Position Independent Code),这是创建动态库所必需的。然后,使用-shared
选项将这些目标文件链接为一个动态库文件。
gcc -fPIC -c add.c
gcc -fPIC -c subtract.c
gcc -fPIC -c multiply.c
gcc -fPIC -c divide.c
gcc -shared -o libmathops.so add.o subtract.o multiply.o divide.o
这些命令会生成libmathops.so
动态库文件。-fPIC
选项是为了确保正确生成动态库所需的可重定位代码。-shared
选项告诉编译器生成动态链接库而不是可执行文件。
步骤 2: 使用动态库
为了使用这个动态库,需要确保编译器在编译时能找到库的头文件,链接器在链接时能找到库文件,运行时动态链接器(如ld-linux.so
)也能找到这个库文件。
还是原来的main.c
文件,内容如下:
#include <stdio.h>
#include "mathops.h"
int main() {
int sum = add(1, 2);
int difference = subtract(10, 5);
int product = multiply(4, 5);
float quotient = divide(20, 4);
printf("Sum: %d\n", sum);
printf("Difference: %d\n", difference);
printf("Product: %d\n", product);
printf("Quotient: %f\n", quotient);
return 0;
}
编译并链接这个程序,同时指定动态库的位置:
gcc -o main main.c -L. -lmathops
如果现在运行会提示找不到动态库
步骤 3: 确保运行时能找到动态库
在运行程序之前,你需要确保动态链接器能找到libmathops.so
。在Linux中,你可以通过设置LD_LIBRARY_PATH
环境变量来实现:
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:.
这会临时添加当前目录到动态链接器的搜索路径中。然后,就可以运行你的程序了:
./main
如果一切正常,你的程序会使用动态库中的函数,并输出正确的结果。
注意
- 将动态库放在标准库路径(如
/usr/lib
或/lib
)或使用ldconfig
命令更新动态链接器的缓存,可以避免设置LD_LIBRARY_PATH
。 - 分发包含动态库的程序时,确保接收方也有途径获取并正确安装这个库。
使用export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:.
命令临时添加当前目录到动态链接器的搜索路径中,这个设置的有效期限是当前终端会话的生命周期。换句话说,这个设置只在你当前打开的这个终端窗口(或会话)中有效。一旦你关闭这个终端窗口或开启一个新的终端会话,这个设置就会失效,LD_LIBRARY_PATH
环境变量会恢复到其默认值或是在其他地方(如你的shell配置文件中)设定的值。
我们现在重新打开命令窗口,试着运行一下main 提示找不到
试着将动态库拷贝到/usr/lib
或/lib 目录下 (现在是拷贝到根文件的lib目录下)
运行成功
windows下静态库的制作
安装windows版gcc
在Windows下制作静态库通常涉及到使用Microsoft Visual Studio的编译器(MSVC)或MinGW(一个适用于Windows的GCC版本)。这里使用MinGW GCC
首先需要安装MinGW,并下载安装gcc,其次配置环境变量
https://sourceforge.net/projects/mingw-w64/files/
安装参考链接:https://blog.csdn.net/jjxcsdn/article/details/123058745?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522170995448716800185835800%2522%252C%2522scm%2522%253A%252220140713.130102334…%2522%257D&request_id=170995448716800185835800&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2alltop_positive~default-1-123058745-null-null.142v99pc_search_result_base7&utm_term=MinGW&spm=1018.2226.3001.4187
有的是下载MinGW选择安装,这个添加环境变量直接使用
在Windows平台上,静态库文件通常有两种后缀:.lib
和 .a
。这两种后缀的主要区别在于它们通常与不同的开发工具链相关联,尽管它们的功能和目的非常相似。
.lib
文件
- 开发工具链:
.lib
文件通常与Microsoft Visual Studio和其他基于Windows的开发环境相关联。 - 用途:在这些环境中,
.lib
文件用作静态库,包含了可直接链接到最终可执行文件的编译代码。此外,.lib
文件也可以作为动态链接库(DLL)的导入库,其中包含了必要的信息以便在编译时解析对DLL中函数的引用,但实际的函数实现在相应的.dll
文件中。
.a
文件
- 开发工具链:
.a
文件通常与GCC(GNU Compiler Collection)相关联,包括在Windows上使用的MinGW(Minimalist GNU for Windows)或Cygwin这样的工具。 - 用途:在这些环境中,
.a
文件用作静态库,包含了可以被链接器整合到最终可执行文件中的编译代码。
区别和兼容性
- 主要区别:这两种文件格式的主要区别在于它们各自支持的工具链。
.lib
文件格式紧密与Microsoft的工具链集成,而.a
文件格式则与GCC和类Unix环境的工具链集成。 - 兼容性:尽管这两种文件在格式上可能有所不同,但它们的基本目的和功能是相同的——都是为了提供一种方式,将预编译的代码库整合到最终的应用程序中。在某些情况下,特定的工具可能能够读取或转换这两种格式之间的文件,但这通常不是默认行为,可能需要额外的步骤或工具。
选择哪一个
- 选择
.lib
还是.a
主要取决于你使用的开发环境和工具链。如果你主要在Visual Studio或其他基于Windows的环境中开发,.lib
可能是更自然的选择。如果你使用GCC、MinGW或Cygwin等工具,.a
文件可能更合适。 - 在跨平台开发中,如果可能,提供两种格式的静态库可以增加库的可用性,让不同环境下的开发者都能方便地使用你的库。
使用gcc工具链命令与linux下的一样,这里不做阐述,演示lib的静态库如何创建(不使用gcc工具链) 使用keil软件生成静态链接库
新建工程(ARM架构随便选)–》加入四个文件
输出勾选lib文件,在输出的文件中找到lib
将lib文件添加到其他工程中包含头文件即可调用
https://txbook.readthedocs.io/zh-cn/latest/keil_output_lib.html
windows下动态库的制作
还是刚才的文件
gcc -shared -o mathops.dll add.c subtract.c multiply.c divide.c -Wl,--out-implib,libmathops.a
gcc -o main.exe main.c -L. -lmathops
./main
其中
gcc -shared -o mathops.dll add.c subtract.c multiply.c divide.c -Wl,--out-implib,libmathops.a 解释
这条命令使用GCC编译器在Windows平台上创建一个动态链接库(DLL)文件mathops.dll
,并且同时生成一个导入库libmathops.a
。这个命令涵盖了编译和链接两个阶段,具体解释如下:
gcc
:调用GNU编译器集合(GCC),这是一个支持多种编程语言的编译器,常用于编译C和C++程序。-shared
:这个选项告诉GCC生成一个共享库(在Windows上是DLL)。共享库可以被多个程序在运行时共享,这与静态库(在程序编译时整合进可执行文件)相对。-o mathops.dll
:指定输出文件的名称和类型。这里,输出文件被命名为mathops.dll
,.dll
扩展名表示这是一个Windows动态链接库文件。add.c subtract.c multiply.c divide.c
:这些是源代码文件,包含了你想要编译进动态库的函数定义。在这个例子中,假设这些文件分别定义了加法、减法、乘法和除法操作。-Wl,--out-implib,libmathops.a
:这个选项用于向链接器(ld)传递参数:-Wl,
:告诉GCC将后面的选项传递给链接器。--out-implib,libmathops.a
:指示链接器生成一个导入库(libmathops.a
),这个库在将来链接到这个DLL的时候会用到。导入库包含了动态库中公开函数的符号引用,使得其他使用这个DLL的程序可以在编译时解析这些符号。
文件下载
https://newbie-typora.oss-cn-shenzhen.aliyuncs.com/zhongke/language_c.zip