静态链接和动态链接 -- 静态加载(隐式调用)和动态加载(显式调用)

news2024/11/16 9:18:48

区别

  • 静态链接和动态链接
    • 静态链接 : 由链接器在链接时将库的内容加入到可执行程序中,这里的库是静态库,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
    在这里插入图片描述

总结

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/657009.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

OpenShift Virtualization - 通过外部固定 IP 访问 VM 中的服务(附视频)

《OpenShift / RHEL / DevSecOps 汇总目录》 说明&#xff1a;本文已经在 OpenShift 4.12 的环境中验证 文章目录 方法1&#xff1a;通过 Service 的 NodePort 访问 VM方法2&#xff1a;通过外部 IP 访问 VM确认 OpenShift 集群环境为 Worker 节点添加 Linux Bridge创建使用 Li…

『DevOps最佳实践』使用Jenkins和Harbor进行持续集成和交付的解决方案

&#x1f4e3;读完这篇文章里你能收获到 全文采用图文形式讲解学会使用Harbor配置项目学会在Jenkins中配置Harbor推送权限使用Jenkins和Harbor进行持续集成的实践感谢点赞收藏&#xff0c;避免下次找不到~ 文章目录 一、准备工作1. 环境准备2. 修改Docker配置文件3. Docker登陆…

2023蓝桥杯大学A组C++国赛游记+个人题解

Day0 发烧了一晚上没睡着&#xff0c;感觉鼻子被打火机烧烤一样难受&#xff0c;心情烦躁 早上6点起来吃了个早饭&#xff0c;思考能力完全丧失了&#xff0c;开始看此花亭奇谭 看了六集&#xff0c;准备复习数据结构考试&#xff0c;然后秒睡 一睁眼就是下午2点了 挂了个…

04- c语言数组 (C语言)

一 数组的概念 1、在程序设计中&#xff0c;为了方便处理数据把具有相同类型的若干变量按有序形式组织起来,这些按序排列的 同类数据元素的集合 称为 数组。 2、在C语言中&#xff0c;数组属于构造数据类型。一个数组可以分解为多个数组元素&#xff0c;这些数组元素可以是基本…

力扣动态规划专题(三)完全背包 518.零钱兑换II 377. 组合总和 Ⅳ 70. 爬楼梯 322. 零钱兑换 279.完全平方数 步骤及C++实现

文章目录 完全背包一维dp数组 滚动数组 518.零钱兑换II377. 组合总和 Ⅳ70. 爬楼梯322. 零钱兑换279.完全平方数139.单词拆分 完全背包 完全背包的物品数量是无限的&#xff0c;01背包的物品数量只有一个 完全背包和01背包分许步骤一样&#xff0c;唯一不同就是体现在遍历顺序上…

deque的介绍

前言 为什么会存在deque呢&#xff1f;在c标准库中deque是作为 stack和queue的底层容器就是deque&#xff0c;我们要是了解过list和vector就会知道这两种容器各有优劣&#xff0c;vector的优点是支持随机访问&#xff0c;进而可以支持排序和二分查找等算法&#xff0c;它的缺点…

鼠标事件 获取鼠标坐标及点击事件

运行效果&#xff1a; 头文件 #ifndef MOUSEEVENT_H #define MOUSEEVENT_H #include #include #include #include class MouseEvent : public QMainWindow { Q_OBJECT public: MouseEvent(QWidget *parent 0); ~MouseEvent(); protected: void mousePressEvent(QMouse…

Linux:一键搭建ftp服务(vsftpd)

《TRO-23614-VSFTPD》 如果csdn收费 可以和我si liao 我会免费发给你 我发的这个是一个tar归档包&#xff0c;脚本就在其中 Linux&#xff1a;《tar》归档命令_鲍海超-GNUBHCkalitarro的博客-CSDN博客 tar xfz tarro_vsftpd.tar.gz -C /root/ # tar xfz tar包路径 -…

SpringBoot第27讲:SpringBoot集成MySQL - MyBatis 多个数据源

SpringBoot第27讲&#xff1a;SpringBoot集成MySQL - MyBatis 多个数据源 本文是SpringBoot第27讲&#xff0c;在某些场景下&#xff0c;Springboot需要使用多个数据源&#xff0c;以及某些场景会需要多个数据源的动态切换。本文主要介绍上述场景及 SpringBootMyBatis实现多个数…

【期末满分作业】C语言程序设计 实训1——奖学金评定系统的设计与实现(附带实验报告、源码以及解释)

大家好&#xff0c;各位努力奋斗的大学生小伙伴们&#xff01;今天&#xff0c;我将带你们领略一项令人惊叹的程序设计奇迹——《奖学金评定系统》&#xff01;是不是感到激动呢&#xff1f;别急&#xff0c;让我为你们揭开这个能让你在C语言程序设计中拿满分的秘密武器&#x…

ASP.NET Core MVC 从入门到精通之Identity入门

随着技术的发展&#xff0c;ASP.NET Core MVC也推出了好长时间&#xff0c;经过不断的版本更新迭代&#xff0c;已经越来越完善&#xff0c;本系列文章主要讲解ASP.NET Core MVC开发B/S系统过程中所涉及到的相关内容&#xff0c;适用于初学者&#xff0c;在校毕业生&#xff0c…

pikache靶场通关——XSS漏洞

文章目录 前言环境第一关、反射型xss(get)Step.1、输入特殊字符测试Step.2、输入js语句Step.3、在URL中输入js语句 第二关、反射性xss(post)Step.1、输入获取cookie的js语句 第三关、存储型xssStep.1、输入获取cookie的js语句Step.2、查看页面源码Step.3、感受危害性 第四关、D…

1 君正IPC芯片方案介绍

专栏特色 1、所有源码严格遵守统一的编码规范。 2、手把手教学&#xff0c;让你从零开始&#xff0c;深入了解君正方案IPC库的方方面面。 3、纯C接口&#xff0c;接口封装严谨&#xff0c;接口功能丰富&#xff0c;应用层调用简单便捷。 4、近二十年行业经验和技术积累打造的高…

风电光伏iEEE33节点蒙特卡洛概率潮流计算

基于蒙特卡洛法的概率潮流 以IEEE33节点的电网为研究对象 建立了光伏和风电的概率出力模型 采用蒙特卡洛法进行随机抽样 之后基于抽样序列进行概率潮流计算 最后得到电网的电压概率出力曲线

使用数字钥匙技术的车辆有多安全?

首发微信公众号网络研究院&#xff0c;关注获取更多。 虽然有几种不同的方法来实现汽车使用的数字钥匙&#xff0c;但安全的数字钥匙标准应该利用近场通信 (NFC) 和超宽带 (UWB) 结合蓝牙低功耗 (BLE) 来访问车辆&#xff0c;开始引擎&#xff0c;固定车辆&#xff0c;或授权各…

云原生之使用Docker部署Dashy个人导航页

云原生之使用Docker部署Dashy个人导航页 一、Dashy介绍1.1 Dashy简介1.2 Dashy特点 二、本地环境介绍2.1 本地环境规划2.2 本次实践介绍 三、本地环境检查3.1 检查Docker服务状态3.2 检查Docker版本3.3 检查docker compose 版本 四、部署前准备工作4.1下载Dashy源码包4.2 查看D…

vue基础-ref (!)

&#xff01;1、ref 引用 在父组件中直接去调 子组件中的方法&#xff0c;使用ref 很简单&#xff0c;比父子传值 简单很多 1、使用ref引用DOM元素 第一步&#xff1a;给标签里 写 ref“xxx” 第二步&#xff1a;就可以用this.$refs.xxx 来拿到这个元素 使用 ! 2、使用ref引…

Spring - Bean的6种作用域

1、Bean作用域问题2、作用域定义2.1、Bean的6种作用域singleton 单例模式prototype 原型作用域request 请求作用域session 会话作用域application 全局作用域&#xff08;了解&#xff09;websocket 单例作用域 vs 全局作用域 2.设置作用域 1、Bean作用域问题 通过一个案例来看…

[工业互联-10]:PLC入门简介

目录 前言 PLC的用途 PLC的特点 PLC的分类 1、按PLC的控制规模分类 2、按PLC的控制性能分类 3、按PLC的结构分类 PLC的技术指标 1、硬件指标 2、软件指标 3、主要性能指标介绍 1) 存储容量 2) 输入/输出&#xff08;I/O&#xff09;点数 3) 扫描速度 4) 指令的功…

【Redis】五种数据结构

在内存种种存储形式如下&#xff1a;