C++跨DLL内存所有权问题探幽(二)CRT中MT和MD混用导致的所有权问题

2023-11-10 13:15

本文主要是介绍C++跨DLL内存所有权问题探幽(二)CRT中MT和MD混用导致的所有权问题,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

0xC0000374: 堆已损坏。 (参数: 0x00007FFA1E9787F0)。
_Mem 是 nullptr

我在开发的过程中有遇到上面两个东西的bug,百思不得其解,最后才发现这个和两个DLL中的MT和 MD选项有关系。

具体情境时:我在一个MT编译的DLL A中引用了一个MD编译的DLL B,并且在A的头文件中声明了一个B对象,这段代码在使用的过程中产生了所有权问题,导致了上述的两个问题。

在正式讨论这个问题之前,需要做一些知识储备

什么是MD和MT编译?

在C++中,MD(Multi-threaded DLL)和MT(Multi-threaded)是Microsoft Visual C++编译器提供的不同的运行时库选项。它们在处理线程、内存管理和链接方式上有所不同。

Multi-threaded DLL(MD):

相当于在编译的时候不将DLL依赖的DLL放在其内部。

MD选项意味着您的应用程序将使用动态链接的多线程C/C++运行时库(CRT)。这意味着您的应用程序将与系统共享这些运行时库。这可以减少最终生成的可执行文件的大小,因为它们不会包含整个运行时库的副本。

运行时库的版本由操作系统决定。如果系统中已经安装了相应版本的运行时库,那么您的应用程序将可以共享这些库,而不需要额外的安装。

Multi-threaded(MT):

相当于在编译的时候将DLL依赖的DLL放在其内部。

MT选项意味着您的应用程序将使用静态链接的多线程C/C++运行时库(CRT)。这意味着您的应用程序将包含完整的运行时库的副本,因此可能会增加最终生成的可执行文件的大小。
运行时库会随着应用程序一起分发,因此用户在运行应用程序之前不需要安装任何其他组件。
这些运行时库负责处理诸如内存管理、线程管理、异常处理和其他与C/C++编程相关的任务。它们提供了诸如动态内存分配和释放、线程同步机制、异常处理等功能。选择使用哪种运行时库取决于项目的需求,以及对最终可执行文件大小和依赖性的要求。选择不同的运行时库可能会影响应用程序的性能和行为。

关于DLL引用

书接上文C++跨DLL内存所有权问题探幽(一)DLL提供的全局单例模式
我们知道一个程序有堆栈啊这些内存空间。

在C++开发中,堆空间和栈空间是用来存储变量和对象的两个主要内存区域。当一个进程引用一个DLL(动态链接库)时,在头文件中声明一个对象和声明一个指针有一些关键区别:

  1. 对象声明:

如果您在头文件中声明一个对象,它将分配在栈空间中。这意味着对象的生命周期将受限于其所在的作用域。当对象所在的作用域结束时,对象将被自动销毁并释放其占用的内存。

如果对象是在动态链接库中定义的,那么在引用动态链接库的程序中,对象的定义和实现将被复制到主程序中。这可能会导致重复定义的问题。

  1. 指针声明:

如果您在头文件中声明一个指针,它将分配在栈空间中。但是指针所指向的对象可能分配在堆空间中,特别是如果您在动态链接库中使用new关键字来动态分配内存。

通过使用指针,您可以在程序中传递对象的引用而不是实际的对象本身。这使得对象可以在堆上动态分配,并且可以在不同的模块之间共享。

也就是说

为什么崩溃?

当在C++中混用MD(Multi-threaded DLL)和MT(Multi-threaded)的DLL时,可能会导致内存冲突和崩溃的主要原因在于堆栈空间的所有权问题。

对堆空间而言

对于MD编译的DLL,它使用的是共享的动态链接的多线程C/C++运行时库,这意味着它使用了操作系统提供的堆管理机制来分配和释放内存。如果您在MD编译的DLL中分配了一块堆内存,它实际上是由操作系统的运行时库进行管理的。

对于MT编译的DLL,它使用的是静态链接的多线程C/C++运行时库,这意味着它会包含自己的堆管理机制。如果您在MT编译的DLL中分配了一块堆内存,它将由该DLL的运行时库管理。

栈空间:

栈空间的所有权归属于当前线程。当您在MD和MT编译的DLL之间切换时,栈空间的所有权可能会发生变化。如果一个线程在MD编译的DLL中分配了一块栈内存,然后在MT编译的DLL中尝试释放它,或者反之亦然,就会产生内存冲突,导致不可预测的行为和可能的崩溃。

因此,在混用MD和MT编译的DLL时,由于堆空间和栈空间的所有权归属和管理方式不同,可能会导致内存的冲突。这种冲突可能会引起一系列问题,包括内存泄漏、指针悬空、数据损坏等,最终导致程序崩溃或产生不可预测的行为。为避免这种情况,请确保在整个应用程序中使用相同类型的运行时库编译所有的DLL。

参考:MD(d)、MT(d)编译选项,在使用Release编译的话不会触发这个崩溃问题。

这篇关于C++跨DLL内存所有权问题探幽(二)CRT中MT和MD混用导致的所有权问题的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/382845

相关文章

Java调用C++动态库超详细步骤讲解(附源码)

《Java调用C++动态库超详细步骤讲解(附源码)》C语言因其高效和接近硬件的特性,时常会被用在性能要求较高或者需要直接操作硬件的场合,:本文主要介绍Java调用C++动态库的相关资料,文中通过代... 目录一、直接调用C++库第一步:动态库生成(vs2017+qt5.12.10)第二步:Java调用C++

SpringBoot首笔交易慢问题排查与优化方案

《SpringBoot首笔交易慢问题排查与优化方案》在我们的微服务项目中,遇到这样的问题:应用启动后,第一笔交易响应耗时高达4、5秒,而后续请求均能在毫秒级完成,这不仅触发监控告警,也极大影响了用户体... 目录问题背景排查步骤1. 日志分析2. 性能工具定位优化方案:提前预热各种资源1. Flowable

C/C++错误信息处理的常见方法及函数

《C/C++错误信息处理的常见方法及函数》C/C++是两种广泛使用的编程语言,特别是在系统编程、嵌入式开发以及高性能计算领域,:本文主要介绍C/C++错误信息处理的常见方法及函数,文中通过代码介绍... 目录前言1. errno 和 perror()示例:2. strerror()示例:3. perror(

C++变换迭代器使用方法小结

《C++变换迭代器使用方法小结》本文主要介绍了C++变换迭代器使用方法小结,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧... 目录1、源码2、代码解析代码解析:transform_iterator1. transform_iterat

详解C++中类的大小决定因数

《详解C++中类的大小决定因数》类的大小受多个因素影响,主要包括成员变量、对齐方式、继承关系、虚函数表等,下面就来介绍一下,具有一定的参考价值,感兴趣的可以了解一下... 目录1. 非静态数据成员示例:2. 数据对齐(Padding)示例:3. 虚函数(vtable 指针)示例:4. 继承普通继承虚继承5.

C++中std::distance使用方法示例

《C++中std::distance使用方法示例》std::distance是C++标准库中的一个函数,用于计算两个迭代器之间的距离,本文主要介绍了C++中std::distance使用方法示例,具... 目录语法使用方式解释示例输出:其他说明:总结std::distance&n编程bsp;是 C++ 标准

springboot循环依赖问题案例代码及解决办法

《springboot循环依赖问题案例代码及解决办法》在SpringBoot中,如果两个或多个Bean之间存在循环依赖(即BeanA依赖BeanB,而BeanB又依赖BeanA),会导致Spring的... 目录1. 什么是循环依赖?2. 循环依赖的场景案例3. 解决循环依赖的常见方法方法 1:使用 @La

Python如何使用__slots__实现节省内存和性能优化

《Python如何使用__slots__实现节省内存和性能优化》你有想过,一个小小的__slots__能让你的Python类内存消耗直接减半吗,没错,今天咱们要聊的就是这个让人眼前一亮的技巧,感兴趣的... 目录背景:内存吃得满满的类__slots__:你的内存管理小助手举个大概的例子:看看效果如何?1.

SpringBoot启动报错的11个高频问题排查与解决终极指南

《SpringBoot启动报错的11个高频问题排查与解决终极指南》这篇文章主要为大家详细介绍了SpringBoot启动报错的11个高频问题的排查与解决,文中的示例代码讲解详细,感兴趣的小伙伴可以了解一... 目录1. 依赖冲突:NoSuchMethodError 的终极解法2. Bean注入失败:No qu

MySQL新增字段后Java实体未更新的潜在问题与解决方案

《MySQL新增字段后Java实体未更新的潜在问题与解决方案》在Java+MySQL的开发中,我们通常使用ORM框架来映射数据库表与Java对象,但有时候,数据库表结构变更(如新增字段)后,开发人员可... 目录引言1. 问题背景:数据库与 Java 实体不同步1.1 常见场景1.2 示例代码2. 不同操作