区别
- 静态链接和动态链接
- 静态链接 : 由链接器在链接时将库的内容加入到可执行程序中,这里的库是静态库,Windows下是*.lib后缀,Linux下是*.a后缀。
- 动态链接 : 可执行程序加载时(静态加载) 或者 运行时(动态加载),将库文件中的内容加入到可执行程序中,这里的库是动态库,Windows下是*.dll后缀,Linux下是*.so后缀。
- 静态加载和动态加载
- 首先,静态加载和动态加载都是动态链接,跟静态链接没有关系。静态加载和动态加载指的都是动态链接的方式。也称为显式调用和隐式调用。
- 静态加载(隐式调用) : 由编译器完成对动态库的加载和卸载工作。编译阶段需要添加头文件,编译器根据动态库路径取查找动态库。程序运行时,如果找不到动态库就会报错。
- 动态加载(显式调用) : 是由运行的程序自行决定什么时候加载或卸载动态库的,编译的时候无需添加头文件等。程序运行时,即使找不到动态库,也能正常执行。
- 下面就分别介绍下Linux平台和Windows平台的库的静态链接和动态链接,以及动态库的静态加载和动态加载。
Linux文件内容
- 库文件内容
- myadd.h
-
#ifndef __MYADD_H__ #define __MYADD_H__ int myAdd(int a, int b); int myMinus(int a, int b); #endif - myadd.c
-
#include "myadd.h" int myAdd(int a, int b){ return a + b; } int myMinus(int a, int b){ return a - b; }
- 源文件内容
- main.c
-
#include "myadd.h" #include <stdio.h> int main(){ int data1 = myAdd(10, 20); printf("data1 = %d\n", data1); int data2 = myMinus(10, 20); printf("data2 = %d\n", data2); return 0; }
Linux库文件制作
- 制作静态库
- gcc -c myadd.c -o myadd.o
- ar rcs libsmyadd.a myadd.o
- 制作动态库
- gcc -c myadd.c -o myadd.o -fPIC
- gcc -shared -o libdmyadd.so myadd.o
- 这里分别制作一个静态库 libsmyadd.a 和一个动态库 libdmyadd.so
Linux静态链接
- 静态链接
- gcc main.c -o ress -L ./ -lsmyadd
- 可以使用ldd ress命令查看可执行文件的依赖库,可以看到依赖库都是系统库。

- 使用nm ress命令查看下符号信息,可以看到可执行文件ress中有对应的函数

Linux动态链接(静态加载)
- 生成可执行程序。
- gcc main.c -o resd -L ./ -ldmyadd -Wl,-rpath=.
- 这里的rpath是指定动态库的加载路径,如果不指定,会去系统库目录下加载。L指定的是动态库的链接路径,这里要注意区分。不指定rpath,编译时不会报错,但运行时会报找不到动态库。如果不指定L,程序编译阶段就会报错。
- 使用ldd resd命令看下可执行程序的依赖,可以看到,这个时候可执行程序需要依赖动态库。

- 使用nm resd命令查看下符号信息,可以看到可执行文件resd中有对应的函数

Linux动态链接(动态加载)相关函数介绍
- void *dlopen(const char *filename, int flag);
- 函数功能:打开或者加载一个动态链接库
- 参数
- filename : 动态库文件名
- flag : 动态库加载方式
- 返回值 : 失败返回NULL
- void *dlsym(void *handle, const char *symbol);
- 函数功能 : 从动态链接库中获取符号地址
- 参数
- handle : dlopen打开的动态库的句柄
- symbol : 符号,也就是动态库中的函数名
- 返回值:失败返回NULL
- int dlclose(void *handle);
- 函数功能 : 关闭或者卸载一个动态链接库
- char *dlerror(void);
- 函数功能 : 获取错误信息
Linux动态链接(动态加载)
- 动态加载时,需要对源文件main.c作以下修改
-
#include <stdio.h> #include <dlfcn.h> //定义函数指针 typedef int (*PADD)(int a, int b); typedef int (*PMINUS)(int a, int b); int main(){ //动态加载so库 void *handle = dlopen("./libdmyadd.so", RTLD_NOW); if(handle == NULL){ printf("load libdmyadd.so failed, errmsg is %s\n", dlerror()); return -1; } PADD pAdd = (PADD)dlsym(handle, "myAdd"); if(pAdd == NULL){ printf("load myAdd func failed, errmsg is %s\n", dlerror()); return -1; } PMINUS pMinus = (PMINUS)dlsym(handle, "myMinus"); if(pMinus == NULL){ printf("load myMinus func failed, errmsg is %s\n", dlerror()); return -1; } int data1 = pAdd(10, 20); printf("data1 = %d\n", data1); int data2 = pMinus(10, 20); printf("data2 = %d\n", data2); //动态卸载so库 dlclose(handle); return 0; } - 可以看到,动态加载时不需要包含动态库的头文件
- 编译命令:gcc main.c -o a.out -ldl
- 编译时,也不需要依赖动态库,但要加一个编译参数 -ldl
- 使用ldd a.out命令看下可执行程序的依赖

- 可以看到,不需要依赖对应的库文件。
- 使用nm a.out命令,可以看到可执行程序中也没有动态库文件中对应的符号

制作Windows静态库
- 新建一个Win32项目

- 这里选择静态库

- 建好工程后,分别添加一个头文件myAdd.h和源文件myAdd.cpp
- myAdd.h
-
#pragma once int myAdd(int a, int b); int myMinus(int a, int b); - myAdd.cpp
-
#include "myAdd.h" int myAdd(int a, int b) { return a + b; } int myMinus(int a, int b) { return a - b; } - 然后点击生成,就会生成一个静态库文件 staticlib.lib

Windows静态加载
- 再新建一个项目

- 这里选择控制台应用程序

- 创建好工程后,添加一个源文件 main.cpp
-
#include <stdio.h> #include <stdlib.h> #include "myAdd.h" //加载静态库 #pragma comment(lib, "staticlib.lib") int main() { int sum1 = myAdd(10, 22); printf("sum1 = %d\n", sum1); int sum2 = myMinus(10, 20); printf("sum2 = %d\n", sum2); system("pause"); return 0; } - 然后需要把刚才制作的静态库staticlib.lib以及对应的头文件myAdd.h拷贝到当前工程目录下,然后编译即可运行。
- 可以使用dependency工具查看可执行文件的依赖,都是依赖的系统库。

制作Windows动态库
- 新建一个项目

- 这里选择DLL
- - 建好工程后,分别添加一个头文件myAdd.h和源文件myAdd.cpp
- myAdd.h
-
#pragma once /* 特别说明 * 导出函数时(也就是生成动态库时),要用_declspec(dllexport)声明函数 * 导入函数时(也就是使用动态库时),要用_declspec(dllimport)声明函数 * 但由于我们生成和使用动态库时,使用的是同一个头文件,所以这里声明函数时要做宏控制进行条件编译 */ #ifdef _DLLAPI #define DLLAPI _declspec(dllexport) #else #define DLLAPI _declspec(dllimport) #endif //注意这里要使用extern "C"去声明,用c编译器去编译 //因为我们知道,c++有多态,c++编译器编译函数时,不仅会把名字编译进去,把参数个数和类型也会编译进去 //因此,如果用c++编译器编译,除了函数名还有其他一些特殊符号。 extern "C" DLLAPI int myAdd(int a, int b); extern "C" DLLAPI int myMinus(int a, int b); - myAdd.cpp
-
#include "myAdd.h" int myAdd(int a, int b) { return a + b; } int myMinus(int a, int b) { return a - b; } - 编译前要添加一个宏定义_DLLAPI,这样生成动态库时就是导出符号。

- 点击生成,就会生成一个静态库dynamiclib.lib和动态库dynamiclib.dll

Windows动态链接(静态加载)
- 新建一个项目

- 选择控制台应用程序

- 添加一个源文件main.cpp
-
#include <stdio.h> #include <stdlib.h> #include "myAdd.h" //加载静态库 #pragma comment(lib, "dynamiclib.lib") int main() { int sum1 = myAdd(10, 22); printf("sum1 = %d\n", sum1); int sum2 = myMinus(10, 20); printf("sum2 = %d\n", sum2); system("pause"); return 0; } - 然后我们需要将静态库dynamiclib.lib和动态库dynamiclib.dll,以及头文件myAdd.h一起拷贝到当前工程目录下,然后再进行编译。
- 可以看下可执行程序的依赖,需要依赖动态库dynamiclib.dll

Windows动态链接(动态加载)
- 新建一个项目

- 选择控制台应用程序

- 建好工程后,添加一个源文件main.cpp
-
#include <stdio.h> #include <stdlib.h> #include <windows.h> //定义函数指针 typedef int(*PADD)(int a, int b); typedef int(*PMINUS)(int a, int b); int main() { //加载动态库 HMODULE hDll = LoadLibrary(L"dynamiclib.dll"); if (hDll == NULL) { printf("加载testdll.dll失败\n"); return -1; } //获取动态库中函数地址 PADD pAdd = (PADD)GetProcAddress(hDll, "myAdd"); if (pAdd == NULL) { printf("获取myAdd地址失败\n"); return -1; } PMINUS pMinus = (PMINUS)GetProcAddress(hDll, "myMinus"); if (pMinus == NULL) { printf("获取myMinus地址失败\n"); return -1; } int sum1 = pAdd(10, 20); printf("sum1 = %d\n", sum1); int sum2 = pMinus(10, 20); printf("sum2 = %d\n", sum2); //卸载动态库 FreeLibrary(hDll); system("pause"); return 0; } - 然后只需要将动态库dynamiclib.dll拷贝到当前工程下,就可以运行了。
- 可以看下可执行程序的依赖,并不需要依赖动态库dynamiclib.dll

总结
- 静态链接和动态链接
- 通过以上介绍可以看到,静态链接就是在编译阶段,将静态库中的符号加载到可执行程序中。优点是可执行程序不需要依赖库文件,可以把可执行程序直接发给用户就可以执行。缺点是项目一旦复杂,可执行程序就会非常大,并且违反了模块化编程思想。动态链接的优点就是可以进行模块化编程,项目中修改某模块时,只需要替换对应的动态库就可以了。缺点是可执行程序要依赖很多库文件,实际开发中经常会因为动态库的依赖问题让人头疼。
- 静态加载和动态加载
- 静态加载和动态加载都是动态链接,但动态加载更加灵活,可以在程序运行过程中由开发者决定动态库的加载时机和卸载时机,动态库的加载位置也非常灵活,可以由开发者自己指定,且不需要包含对应的头文件。

本文详细介绍了静态链接和动态链接的区别,以及在Linux和Windows平台上的实现。静态链接将库内容嵌入到可执行文件中,而动态链接则在运行时加载库。动态链接分为静态加载(隐式调用)和动态加载(显式调用),后者允许程序运行时动态加载和卸载库。通过示例展示了如何在Linux和Windows上创建静态库和动态库,以及如何进行静态和动态加载。动态加载提供了更高的灵活性,但需要处理库文件的依赖问题。


被折叠的 条评论
为什么被折叠?



