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(动态链接库)时,在头文件中声明一个对象和声明一个指针有一些关键区别:
- 对象声明:
如果您在头文件中声明一个对象,它将分配在栈空间中。这意味着对象的生命周期将受限于其所在的作用域。当对象所在的作用域结束时,对象将被自动销毁并释放其占用的内存。
如果对象是在动态链接库中定义的,那么在引用动态链接库的程序中,对象的定义和实现将被复制到主程序中。这可能会导致重复定义的问题。
- 指针声明:
如果您在头文件中声明一个指针,它将分配在栈空间中。但是指针所指向的对象可能分配在堆空间中,特别是如果您在动态链接库中使用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混用导致的堆损坏的更多相关文章
- 栈 堆 stack heap 堆内存 栈内存 内存分配中的堆和栈 掌握堆内存的权柄就是返回的指针 栈是面向线程的而堆是面向进程的。 new/delete and malloc/ free 指针与内存模型
小结: 1.栈内存 为什么快? Due to this nature, the process of storing and retrieving data from the stack is ver ...
- C++中string跨DLL失败解决途径
1.问题描述: 在一个MFC应用程序exe中,调用另一个DLL中的函数,函数中的一个形参是string类型的,每次调用都会出现乱码的情况. 调用前: 调用后: 2.原因分析: 不同的模块各自有一份C运 ...
- .NET DLL 保护措施详解(二)关于性能的测试
先说结果: 加了缓存的结果与C#原生代码差异不大了 我对三种方式进行了测试: 第一种,每次调用均动态编译 第二种,缓存编译好的对象 第三种,直接调用原生C#代码 .net dll保护系列 ------ ...
- [转]System.DllNotFoundException: 无法加载 DLL“*.dll”: 内存位置访问无效。 (异常来自 HRESULT:0x800703E6)
我在使用地税发票控件进行开票的测试的时候,在xp上测试时正常的,在别人的win7系统测试也是正常,但我在我本机确不正常.我本机装的是msdn版本win7系统,这个系统比较原装. 错误信息如下: -- ...
- 跨DLL操作fopen的返回值导致出错
在设置成/MD或/MDd不会导致出错 设置成/MT或/MTd的情况下会导致出错 看了CRT的实现,估计是因为fopen创建了CriticalSection来保护文件,但是在/MT的情况下,一个DLL里 ...
- DLL内存分配与共享
一旦DLL的文件映像被映射到调用进程的地址空间中,DLL的函数就可以供进程中运行的所有线程使用.实际上,DLL几乎将失去它作为DLL的全部特征.对于进程中的线程来说,DLL的代码和数据看上去就像恰巧是 ...
- C++ 跨dll传递string类型参数执行出错问题
今天遇到一个问题,在一个dll工程中定义了一个返回值为string,参数为string的函数,然后在一个测试工程中调用,Release模式下一切正常Debug模式下整个函数的执行到return之前都毫 ...
- 第十七篇 ORM跨表查询和分组查询---二次剖析
ORM跨表查询和分组查询---二次剖析 阅读目录(Content) 创建表(建立模型) 基于对象的跨表查询 一对多查询(Publish与Book) 多对多查询 (Author 与 Book) 一对一查 ...
- Linux内核中常见内存分配函数(二)
常用内存分配函数 __get_free_pages unsigned long __get_free_pages(gfp_t gfp_mask, unsigned int order) __get_f ...
- 深入理解JAVA虚拟机原理之内存分配策略(二)
更多Android高级架构进阶视频学习请点击:https://space.bilibili.com/474380680 1.对象优先在Eden分配 大多情况,对象在新生代Eden区分配.当Eden区没 ...
随机推荐
- Command "python setup.py egg_info" failed with error code 1 in /tmp/pip-install-4_w0c665/PyQt5/
错误: 解决方式:输入一下命令 1 pip3 install --upgrade setuptools 2 python3 -m pip install --upgrade pip 输入命令: 1 p ...
- C# CEFSharp WCF开发桌面程序实现“同一网站多开”
前言 孔乙己显出极高兴的样子,将两个指头的长指甲敲着柜台,点头说:"对呀,对呀!CEFSharp,你用过么?访问同一网址实现多开怎么实现?比如我有3个淘宝店,我想同时登录维护,就像传说中的指 ...
- [clickhouse]同步MySQL
前言 clickhouse的查询速度非常快,而且兼容大部分MySQL的sql语法,因此一般将clickhouse作为MySQL的读库. 本文提供两种clickhouse同步MySQL的方式 click ...
- quarkus数据库篇之四:本地缓存
欢迎访问我的GitHub 这里分类和汇总了欣宸的全部原创(含配套源码):https://github.com/zq2599/blog_demos 本篇概览 本文是<quarkus数据库篇> ...
- SqlServer表添加字段
IF NOT EXISTS (SELECT * FROM syscolumns WHERE id=object_id('表名') AND name='字段名') ALTER TABLE 表名 ADD ...
- 文心一言 VS 讯飞星火 VS chatgpt (85)-- 算法导论8.2 2题
二.用go语言,试证明 COUNTING-SORT 是稳定的. 文心一言: 要证明 COUNTING-SORT 是稳定的,我们需要证明在排序过程中,具有相同值的元素在排序后仍保持其原始的相对顺序.CO ...
- 1.15 自实现GetProcAddress
在正常情况下,要想使用GetProcAddress函数,需要首先调用LoadLibraryA函数获取到kernel32.dll动态链接库的内存地址,接着在调用GetProcAddress函数时传入模块 ...
- QA|20221010|SecureCRT|我们5分钟前执行了a指令,但因为执行b指令打印了大量日志,把指令记录冲掉了,以后如何避免这种情况?
Q:我们5分钟前执行了a指令,但因为执行b指令打印了大量日志,把指令记录冲掉了,以后如何避免这种情况? A:如下配置
- 循序渐进介绍基于CommunityToolkit.Mvvm 和HandyControl的WPF应用端开发(1)
在我们的SqlSugar的开发框架中,整合了Winform端.Vue3+ElementPlus的前端.以及基于UniApp+Vue+ThorUI的移动前端几个前端处理,基本上覆盖了我们日常的应用模式了 ...
- Confluence的Excel插件Elements Spreadsheet安装
背景 Confluence是现在广泛使用的团队协作文档系统.虽然自身带了一些表格编辑功能,但表格的整体功能较弱,比如不能通过Excel文件进行导入导出,表格在复制到Excel时格式会比较奇怪等等.对于 ...