运行时库链接方式实践指南(MT、MD、MTd、MDd)

前言

笔者曾经编译一个库提供给使用者,提供库后发现由于运行时库连接方式不一致,导致使用者无法连接笔者提供的库。另一方面,理解和选择正确的运行时链接方式对于构建高效、可靠的应用程序至关重要。
因此,本文将展开运行时库的基本概念、讨论不同的运行时链接类型,以及如何根据项目需求选择最合适的链接方式。

什么是运行时?

运行时库(Runtime Library):这是一组标准化的软件函数集合,提供程序运行时所需的基本服务,比如I/O处理、内存管理等。例如标准模板库、C函数库等。

运行时库链接方式

运行时链接方式关主要分为两大类:静态链接和动态链接。

静态链接

静态链接是在编译时期将所有必要的库函数代码直接打包到最终的可执行文件中。静态链接产生的可执行文件通常体积较大,但它们不依赖外部的库文件,提高了应用的独立性和便于部署。

动态链接

与静态链接相对,动态链接是在运行时期加载所需的运行时库(通常是动态链接库DLL或共享对象SO)。动态链接的应用程序体积较小,可以实现库文件的共享使用,节省系统资源。

如何选择链接方式

选择链接方式时,需要综合考虑多个因素:

  • 部署简易性:如果需要简化部署过程,不希望处理外部的运行时库依赖,静态链接是一个不错的选择。

  • 体积和资源占用:如果对可执行文件的体积和内存占用有严格要求,动态链接可以减少重复的库代码占用。

  • 更新和维护:动态链接便于库文件的更新和维护,特别是当涉及到安全更新和修复时。

  • 兼容性:在有些环境中,动态链接更受欢迎,因为它保证了与系统的兼容性和一致性。

  • 特殊需求:对于需要插件或按需加载功能的应用,运行时动态链接或延迟加载等技术可能更合适。

MSVC实战

MSVC(Microsoft Visual C++)的编译选项MT、MD、MTd、MDd指定了程序是静态链接到运行时库还是动态链接,以及是否是调试版本的库。

以下是每个选项的含义:

  • MT:将程序与多线程版的静态运行时库进行链接。
  • MD:将程序与多线程版的动态运行时库(DLL)进行链接。
  • MTd:将程序与多线程版的静态运行时库进行链接,并且是调试版。
  • MDd:将程序与多线程版的动态运行时库(DLL)进行链接,并且是调试版。

MT 和 MD 分别代表:MT: Multi-Threaded和MD: Multi-Threaded DLL。早期的 Microsoft Visual C++ 版本中,存在单线程版本的运行时库,这些运行时库不支持多线程程序,从 Visual Studio 2005 开始,所有的运行时库都是多线程的(MT 或 MD),单线程的运行时库已经被淘汰。

优缺点

  • MT/MTd(静态链接)的优点是不需要在部署时包含额外的运行时库DLL文件,因为所有的运行时代码都已经包含在最终的可执行文件中。这简化了部署过程并减少了对环境的依赖。缺点是最终的可执行文件会比较大(因为把运行时库二进制也带进来了),如果项目中多个库使用了静态链接运行时库,也会出现重复的、冗余的代码占用内存。
  • MD/MDd(动态链接)的优点是可执行文件体积较小,多个程序可以共享同一份运行时库的副本,节省资源。缺点是在部署程序时需要确保正确版本的运行时库DLL文件也被安装在目标系统上,否则程序无法运行。

如果使用动态链接,但是没有打包运行时库会怎样?

许多开发者都可能遇到忘记打包运行时库的场景,表现上是在自己电脑上正常运行,但是发布到其他电脑上提示找不到xxx.dll,例如:
在这里插入图片描述
网上也提供了很多接近方案,例如VC Runtime 集合,包含了各种版本的 Visual C++ 运行时库的集合。如果一个应用程序是使用特定版本的 Visual Studio 编译的,并且使用了动态链接库(DLL),那么它就需要相应版本的 VC Runtime 来确保正确运行。

VC Runtime 集合的出现主要是为了解决以下问题:

  1. 版本兼容:不同的应用程序可能需要不同版本的 VC Runtime。一个集合包含了多个版本的运行时库,可以确保大多数应用程序的兼容性。

  2. 简化安装:用户可以一次性安装多个版本的 VC Runtime,而不需要单独下载和安装每个应用程序需要的特定版本。

  3. 解决缺失:如果用户遇到因为缺少 VC Runtime 导致的问题,安装集合包可以快速解决缺失的库文件问题。

从某种角度上来说,VC Runtime 集合的存在并不直接代表软件开发者没有正确打包动态运行时库,而是因为操作系统或软件的多样性和复杂性,使得拥有一个集合包来解决潜在的兼容性问题变得更加方便和必要。不过,为了减少不必要的用户反馈,最佳实践仍然是我们在软件分发时包括所有必要的依赖,确保自己的软件不出现运行时库的缺少的问题。

其他编译器的情况

在Clang和GCC上的对应选项:它们都支持静态链接和动态链接的概念,但是Clang和GCC在选择运行时库时和MSVC的选项不一样。

  • 在GCC中,你可以使用-static选项来静态链接到库。GCC默认动态链接到glibc。
  • 在Clang中,链接方式的选择也可以通过指定链接器(linker)参数来实现,比如使用-static进行静态链接。

在Linux上,通常默认链接到系统的动态运行时库,因此不需要特别指定。如果需要静态链接,可能需要确保静态版本的运行时库已经安装,并在链接时指定对应的选项。

在使用静态链接时,需要确保遵守所有依赖库的许可协议,因为静态链接可能会将代码合并到可执行文件中。

在编译大型项目中的实践

大型项目,例如QtFramework,在配置时一般都有编译选项支持选择如何链接运行时库,以满足开发者自定义编译的需求。
翻阅Qt源码,可以找到这个选项:
在这里插入图片描述
通过config help也能找到:
在这里插入图片描述
QT默认的编译选项是-MD,当这个选项开启时,会将-MD替换为-MT。
在这里插入图片描述

其他的开源项目也是类似,基本都会有参数可以让开发者自定义选择运行库链接方式,因为如果在库中使用库,那么链接方式必须一样才能被正确链接。

为什么在同一个项目中,库的运行时库的链接方式必须一样

在同一个项目中,所有组件使用相同的运行时库链接方式是非常重要的,主要原因是确保一致性、稳定性和避免潜在的冲突。具体来说:

1. 对象内存管理

不同的运行时库可能有不同的内存管理机制。如果一个对象在一个运行时库中被创建(分配内存),而在另一个运行时库中被销毁(释放内存),可能会导致内存泄漏或者更糟糕的情况。确保所有库使用相同的运行时库可以防止这种情况发生。

2. 全局状态的一致性

静态链接的运行时库将其全局状态编译进每个可执行文件或库中。如果一个项目中混用了静态和动态链接的运行时库,可能导致有多个独立的全局状态实例存在于同一个进程中,这会导致异常行为,比如不一致的全局变量状态、初始化和终止序列混乱等。

3. C++异常处理

在C++中,如果不同的组件使用了不同的运行时库,可能会导致异常处理不兼容。例如,一个用MDd编译的DLL抛出的异常可能无法被用MTd编译的可执行文件正确捕获或处理。

4. 类型定义和实现

运行时库通常包含了标准类型和函数的实现。如果不同的运行时库版本被混合使用,可能会导致类型定义上的冲突或者不同的实现之间的不兼容,导致运行时错误。

5. 线程和同步对象

多线程编程通常依赖于运行时库提供的同步机制,如互斥锁和条件变量。如果混用不同的运行时库,线程同步对象的行为可能会不一致,导致死锁或竞争条件。

6. 依赖和链接问题

如果项目中的不同部分链接了不同的运行时库,可能会出现难以诊断的链接错误,因为链接器可能无法解析多个版本的符号和库。

为了确保软件稳定运行,维护性和可预测性,需要在同一个项目中使用相同的运行时库链接方式。如果必须混用不同的链接方式,往往会有编译器链接报错。

结语

在软件开发中,了解不同运行时的特点并实际应用它们,或者编译出正确的库提供给使用方,是每个软件工程师技能库中不可或缺的一部分。
通过本文系统性的介绍,相信你对运行时库的概念、特点。一致性的重要性有了更深的认识。
如果想要了解如何确定在软件分发时(安装包)需要打包哪些运行时库文件,请参考:《深入解析VC Runtime:什么是vcruntimeXXX.dll和api-ms-win-crt-runtime-X-X-X.dll?》

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

如何写好AI绘画提示词?保姆级教程来了!

前言 提示词编辑是一个结构化的过程,用能被人类解释和理解的词语来描述图像,也就是告诉人工智能模型应该怎么绘制图片。 生成优质图像的秘诀 1.提示词要想编辑好,包括修饰词和好的句子结构,首先你要了解所有的修饰词类型。 2.St…

大模型日报|8 篇必读的大模型论文

大家好,今日必读的大模型论文来啦! 1.M2Lingual:在大语言模型中加强多语言、多轮次的指令对齐 指令微调对于大语言模型(LLM)按照指令进行对齐至关重要。最近提出了许多有效的 IFT 数据集,但大多数数据集都…

HALCON-从入门到入门-提取小票上的斑点

测试效果 在一张超市小票上提取点阵数字 处理步骤解析 首先读取两张图,一张是小票的图片,一张是静脉的图片 为了让点阵数字提取更加困难,我们将两张图片合成到一起 read_image (ImageNoise, angio-part) crop_part (ImageNoise, ImagePart…

在 Postman 中使用 Body 进行 POST 请求

Postman 是开发者日常工具箱中不可缺少的一部分,特别是在 API 开发和调试环节中。 为什么使用 POST 请求 POST 请求用于向服务器发送数据,这些数据通常被处理后存储。与 GET 请求不同,POST 请求将数据嵌入请求体(Body&#xff0…

Linux配置网卡详细教程

这个网卡配置然后头痛了两天,看了很多篇关于这方面的文章,但是都没让我成功,可惜工亏不负有心人,然后终于学会了下面此方法 实现完成的效果: 永久修改网卡IP vi /etc/sysconfig/network-scripts/ifcfg-ens33 TYPEEther…

【Linux】Docker安装kafka教程(超详细保姆篇)

文章目录 安装1.安装Zookeeper2.安装kafka3.创建zookeeper容器4.创建kafka容器5.测试kafka6.退出bash shell Zookeeper服务启动1.首先找到Zookeeper安装路径2.执行./zkServer.sh start3.查看运行状态3.集群配置(可不阅) kafka服务启动1.进入kafka的config目录2.修改server.prop…

后端返回base64文件流下载

后端返回base64文件流: 前端处理: downloadTemplate () {this.$API.downloadTemplate().then(({ data }) > {const binaryString atob(data) // 解码base64字符串const byteArray new Uint8Array(binaryString.length) // 创建一个Uint8Arrayfor (let i 0; i…

Java面试八股之Mybatis可以映射到枚举类吗

Mybatis可以映射到枚举类吗 Mybatis 可以映射到 Java 的枚举类型。默认情况下,Mybatis 会使用枚举类型的名称来进行映射。例如,如果你有一个如下的枚举类型: public enum UserStatus { ACTIVE, INACTIVE } Mybatis 会将数据库中的字符串值…

PointCloudLib (多线程)快速双边滤波 C++版本

0.实现效果 原始点云 和滤波后的点云对比 1.算法原理 PCL(Point Cloud Library)快速双边滤波是一种高效的点云数据滤波方法,它基于传统双边滤波算法进行了改进,通过引入近似方法加速计算过程。以下是关于PCL快速双边滤波的详细回答: 1. 基本原理 空间滤波:在点云中,相…

PointCloudLib 3D对象的可视化 C++版本

0.实现效果 显示箭头 vtkOutputWindow::SetGlobalWarningDisplay(0);pcl::visualization::PCLVisualizer::Ptr viewer(new pcl::visualization::PCLVisualizer("3D Viewer"));viewer->setBackgroundColor(1, 1, 1);//添加箭头显示pcl::PointXYZ pA(0, 0, 0);pcl:…

SpringBoot-在配置文件中使用Profile

Profile,译为“配置文件” 在这里的Spring Boot也是一样,我们可以配置很多个Profile,每个Profile都对应一整个完整的全局配置,激活哪个,那个对应的全局配置就生效,具体的配置: 1、properties格…

常见硬件工程师面试题(二)

大家好,我是山羊君Goat。 对于硬件工程师,学习的东西主要和电路硬件相关,所以在硬件工程师的面试中,对于经验是十分看重的,像PCB设计,电路设计原理,模拟电路,数字电路等等相关的知识…

怎么在线一次压缩多张图片?分享3款简单的在线图片压缩工具

在日常工作和生活中,经常会需要使用图片处理大小功能,网上有很多的图片压缩工具都能够快速处理图片大小,那么当遇到大量的图片需要压缩大小时,该如何操作才能快速在线压缩图片大小呢?多张图片怎么一次批量压缩&#xf…

现在的Java面试都这么扯淡了吗?

在开始前刚好我有一些资料,是我根据网友给的问题精心整理了一份「java的资料从专业入门到高级教程」, 点个关注在评论区回复“666”之后私信回复“666”,全部无偿共享给大家!!!开发兼过半年面试官 刚开始…

算法训练营第七十天 | 最小生成树之prim、最小生成树之Kruskal、拓扑排序

算法训练营第七十天 最小生成树之prim 题目链接:https://kamacoder.com/problempage.php?pid1053 随意将一个节点放入set作为初始状态。每次从和set中节点相连的权值最小的边相连的节点放入并记录权值。直到set大小和节点数相同。 代码如下: #i…

Java核心知识(一):JVM

JVM 前言 文本源自微博客 (www.microblog.store),且已获授权. 一、线程 1.1 基本概念 JVM是可运行java代码的假象计算机,包括一套字节码指令集、一组寄存器、一个栈、一个垃圾回收、堆和一个存储方法域。JVM是运行在操作系统之上的,与硬件没有直接的交互。 1.2 运…

C# 中的 StreamReader 和 StreamWriter 类

在这里插入代码片StreamReader 和 StreamWriter 位于 System.IO 命名空间中。当您想要读取或写入基于字符的数据时,这两个类都很有用。这两个类都处理 Unicode 字符。 StreamReader 派生自抽象类“TextReader”,StreamWriter 派生自“TextWriter”。 下…

金融企业数据跨境流动的核心需求是什么?如何才能落地?

在金融行业,涉及到的数据跨境流动的场景多种多样,主要涉及到金融机构的跨国经营、全球贸易以及服务贸易等多个方面: 企业跨国经营:当金融机构进行跨国经营时,如银行在海外设立分支机构或进行跨境投资,会涉及…

Linux 式套娃,把“文件系统”安装在一个“文件”上?

背景 “文件”在文件系统之中,这是人人理解的概念。但“文件”之上还有一个文件系统?那岂不是成套娃了。但这个其实是可以的。这个就涉及到今天我们要讲的 loop 设备。 很多童鞋在学习 Linux 的文件系统时,涉及到对磁盘设备的格式化&#x…

Spring底层原理之bean的加载方式三 用注解声明配置类 以及@Configuration 和 @Component 的区别

bean的加载方式三 用注解声明配置类 我们之前用组件扫描加上注解定义bean 实现了bean的加载 当我们又会发现这个配置文件过于繁琐 我们可以写一个类 不是配置文件而是配置类 我们接下来只需要把这句话的功能写到 配置类里面 这样书写就行 package com.bigdata1421.config;…