不要混合使用运行时库的静态版本和动态版本

本文详细介绍了在开发过程中遇到的内存释放冲突问题,通过对比不同CRT库的配置(/ML,/MT,/MD,/MLd,/MTd,/MDd)及对BoundersChecker工具的应用,最终发现了问题根源在于DLL模块与exe模块使用的CRT库配置不一致。文章总结了解决方案,并强调了在整个项目中使用统一的CRT库的重要性。同时,讨论了不同CRT库类型(静态链接与动态链接,调试版本与非调试版本)对项目的影响,以及如何避免此类问题的方法。
最近在开发一个项目,有比较多的dll模块,以前测试时一直没有发现问题,运行的很正常。今天要发布一个新版本了,就用Bounders Check检查了一下。乖乖,很多错误阿!全部都是:
   Allocation Conflict: Attempting to call global_operator_delete on 0x01566E68; pointer was allocated by malloc.
真是奇怪了,看了n多代码,找不出问题所在,并且这个错误是在pair<string,string> 发现的,好像根本就不是我的问题。
又经历了n多回的看。。。。。。。

     最后终于发现问题了。原来我所有dll模块在生成时用的是多线程调试(/MDd), 但是我的exe也就是访问dll的模块是用单线程调试(/MLd)生成的,但是有意思的是我有一个测试程式也是用“单线程调试(/MLd)”生成的,运行时确很正常。
     问题解决了,小结一下:
          (1)所有的模块,包括dll/exe等,都需要统一的“C Run-Time(CRT) library ” !!!!
          (2)BoundersChecker真的很管用,能帮你找出很多问题,甚至是很多你都没有意识/察觉到的问题。
           (3) MSVCR71.DLL(C run-time library (without iostream or standard C++ library) ) 和 MSVCP71.dll(Standard C++ Library) 访在和exe文件的同一目录下时,BounderChecker也会报 “Allocation Conflict: Attempting to call global_operator_delete on 0x01566E68; pointer was allocated by malloc.”这个错误。
                MSVCR71.DLL : C run-time library (without iostream or standard C++ library) 
                MSVCP71.dll    :  Standard C++ Library)



附:

msdn:
警告
   不要混合使用运行时库的静态版本和动态版本。在一个进程中有多个运行时库副本会导致问题,因为副本中的静态数据不与其他副本共享。链接器禁止在 .exe 文件内部既使用静态版本又使用动态版本链接,但您仍可以使用运行时库的两个(或更多)副本。例如,当与用动态 (DLL) 版本的运行时库链接的 .exe 文件一起使用时,用静态(非 DLL)版本的运行时库链接的动态链接库可能导致问题。(还应该避免在一个进程中混合使用这些库的调试版本和非调试版本)。


 

在Windows下有六种类型CRTLib(C运行库):
Reusable Library                Switch    Library    Macro(s) Defined
----------------------------------------------------------------
Single Threaded                     /ML       LIBC               (none)
Static MultiThread                  /MT       LIBCMT         _MT
Dynamic Link (DLL)              /MD       MSVCRT        _MT and _DLL
Debug Single Threaded           /MLd      LIBCD          _DEBUG
Debug Static MultiThread        /MTd      LIBCMTD    _DEBUG and _MT
Debug Dynamic Link (DLL)    /MDd      MSVCRTD    _DEBUG, _MT, and _DLL

MT和MD都适用于多线程,其区别是:
MT为静态链接CRT,这样编译出来exe是自包含的,所以会相对大一些,但运行时不用再load CRT库。
MD为动态链接CRT,编译出来exe会小一些,运行时需要load CRT,性能有一点点损失。

任何工程都应该使用同样的CRT Library。即要么都是/ML,要么都是/MTD, 如此类推。

如果一个程序中混合使用不同类型的CRT,有时可以通过link,这样会存在不同CRT的copy,并导致以下问题:
    1)在一个lib中new出来内存,在另一个lib中delete,会crash。
    2)不能在多个lib中共享file handle。
    3)一个lib中设置locale(本地化有关),不能在另一个lib中起作用。

当工程比较大,包含的lib很多,特别当有外部lib(Third party library)存在时,link很容易发生下面这样的错误。
LIBCMTD.lib(chsize.obj) : error LNK2005: __chsize already defined in MSVCRTD.lib(MSVCRTD.dll)
这说明,你的工程使用了不同类型的CRT。这个时候首先一定要坚信这个原则:整个工程用同样的CRT Lib就可以解决问题。然后耐心一一检查每个lib。
如果恰恰某个外部lib用MT,另一个用MD,这个时候就比较痛苦。
    如果有他们源码,就编译一个MT or MD类型的lib,以统一使用一个类型CRT。
    如果没有,那可能只好选择其他的lib。


另有msdn lib关于:
 

请参见Run-Time Library Reference 

C Run-Time Libraries

 

This topic discusses the various .lib files that comprise the C run-time libraries as well as their associated compiler options and preprocessor directives.

The following libraries contain the C run-time library functions.

C run-time library (without iostream or standard C++ library)CharacteristicsOptionPreprocessor directives
LIBC.LIBSingle-threaded, static link/ML 
LIBCMT.LIBMultithreaded, static link/MT_MT
MSVCRT.LIBMultithreaded, dynamic link (import library for MSVCR71.DLL). Be aware that if you use the Standard C++ Library, your program will need MSVCP71.DLL to run./MD_MT, _DLL
LIBCD.LIBSingle-threaded, static link (debug)/MLd_DEBUG
LIBCMTD.LIBMultithreaded, static link (debug)/MTd_DEBUG, _MT
MSVCRTD.LIBMultithreaded, dynamic link (import library for MSVCR71D.DLL) (debug)/MDd_DEBUG, _MT, _DLL

If you link your program from the command line without a compiler option that specifies a C run-time library, the linker will use LIBC.LIB.

To build a debug version of your application, the _DEBUG flag must be defined and the application must be linked with a debug version of one of these libraries. For more information about using the debug versions of the library files, see CRT Debugging Techniques.

This version of Visual C++ is not conformant with the C99 standard.

Standard C++ Library

<think> 我们正在讨论混合使用版本的VC++运行库可能导致的问题。结合引用资料,特别是引用[4]用户提供的上下文,我们可以分析如下: 关键点:每个VC++运行库版本都包含特定的DLL文件(如msvcp140.dll对应VC++2015-2019),这些DLL文件具有同的版本号,且彼此兼容。当应用程序依赖多个版本的运行库时,可能会引发以下问题: 1. **DLL冲突**:如果系统中同时存在多个版本的相同DLL文件(如msvcp140.dll的多个版本),系统可能加载错误的版本,导致应用程序崩溃。例如: - 应用程序A依赖VC++2015运行库(msvcp140.dll版本14.0.23026) - 应用程序B依赖VC++2017运行库(msvcp140.dll版本14.16.27012) - 若应用程序A错误加载了VC++2017的msvcp140.dll,可能因函数签名或内部结构变化而崩溃。 2. **内存管理一致**:版本的运行库使用独立的内存堆(heap)。若在一个模块中分配内存,在另一个模块中释放,且这两个模块使用版本的运行库,会导致堆损坏。例如: ```cpp // 在DLL1(使用VC++2015编译)中分配内存 void* allocate_in_dll1() { return malloc(100); // 使用VC++2015运行库的堆 } // 在DLL2(使用VC++2019编译)中释放内存 void free_in_dll2(void* p) { free(p); // 使用VC++2019运行库的堆 → 崩溃! } ``` 3. **C++对象模型差异**:版本的VC++可能修改C++运行时对象的内存布局(如std::string、std::vector)。跨模块传递此类对象时,若模块使用版本的运行库,会导致未定义行为。例如: ```cpp // 模块A(VC++2015)导出函数 __declspec(dllexport) std::string create_string() { return "Hello"; // 使用VC++2015的std::string布局 } // 模块B(VC++2019)调用该函数 std::string s = create_string(); // 内存布局匹配 → 访问冲突! ``` 4. **初始化顺序问题**:全局对象的构造析构依赖于运行库的初始化。混合版本可能导致初始化顺序错乱,引发崩溃。例如: - VC++2015的全局对象在VC++2019的静态变量之前析构,当后者依赖前者时,访问已销毁对象。 5. **调试版与发布版混合**:调试版运行库(如msvcp140d.dll)与发布版(msvcp140.dll)可混用,否则立即触发断言失败或访问冲突。 ### 解决方案 - **统一编译环境**:确保整个解决方案(主程序+所有依赖库)使用相同版本的Visual Studio编译[^4]。 - **静态链接运行库**:对第三方库,使用/MT(静态链接)编译,避免依赖特定版本的DLL。但需注意许可证问题。 - **应用程序本地部署**:将所需运行库DLL放在exe同级目录(优先于系统目录加载)。 - **使用Windows Side-by-Side Assembly**:通过清单文件指定精确依赖版本。 ### 相关问题 1. 如何检测应用程序依赖的VC++运行库版本? 2. 静态链接动态链接VC++运行库各有什么优缺点? 3. 如何解决因全局对象析构顺序导致的崩溃? 4. 在多模块项目中如何强制统一运行库版本? 5. Windows Side-by-Side Assembly如何配置? > 💡 **最佳实践**:微软建议为每个VC++版本独立打包依赖库,并通过vcruntime140.dll的版本隔离机制避免冲突[^4]。 </think> 混合使用版本的 VC++ 运行库可能导致严重兼容性问题,主要表现及原因如下: ### 常见问题及机制解析 1. **DLL 地狱(DLL Hell)** 当同模块依赖版本的运行库时: - 系统可能加载错误版本的 `msvcp140.dll` 等核心库 - 导致函数入口点错位或内存布局匹配 *示例*: $$ \text{主程序} \xrightarrow{vc2019} \text{msvcp140.dll(v14.28)} + \text{插件} \xrightarrow{vc2015} \text{msvcp140.dll(v14.0)} \implies \text{访问冲突} $$ 2. **内存管理冲突** - 版本运行库使用独立的内存堆(Heap) - 跨模块内存操作导致崩溃: ```cpp // 模块A(VC2015)分配内存 void* p = malloc(100); // 使用VC2015堆管理器 // 模块B(VC2019)释放内存 free(p); // 使用VC2019堆管理器 → 访问冲突! ``` 3. **C++ ABI 兼容** - STL 对象布局差异(如 `std::string` 内部结构变化) - 虚函数表偏移量一致 - *典型崩溃场景*: $$ \text{传递} \ std::vector \xrightarrow{\text{跨版本边界}} \implies \text{0xC0000005} $$ 4. **初始化顺序灾难** 全局对象构造函数在版本运行库中执行顺序可控: ```cpp // VC2019模块 GlobalObjA objA; // 依赖vc2019初始化 // VC2015模块 GlobalObjB objB(objA); // vc2015未初始化完成时使用 → 空指针解引用 ``` ### 解决方案参考引用[3][4] 1. **统一运行库版本** - 使用 `/MT` 静态链接将运行库嵌入可执行文件 - 或强制所有模块依赖相同动态版本(如统一用 VC++2019) 2. **隔离部署策略** ```bash # 应用目录部署特定版本运行库[^3] YourApp/ ├── YourApp.exe └── vcruntime140.dll # 专用版本 ``` 3. **清单文件控制** 通过 manifest 指定精确依赖版本: ```xml <dependency> <dependentAssembly> <assemblyIdentity type="win32" name="Microsoft.VC140.CRT" version="14.28.29910.0"/> </dependentAssembly> </dependency> ``` 4. **兼容性检测** 启动时验证运行库版本[^3]: ```cpp if(GetFileVersion("vcruntime140.dll") < 14.28) ShowError("需安装VC++2019运行库"); ``` --- ### 典型故障案例 | 混合场景 | 崩溃现象 | 根本原因 | |------------------------|-----------------------------|-----------------------| | VC2015 EXE + VC2019 DLL | 传递STL对象时访问冲突 | `std::string` ABI变更 | | Debug EXE + Release DLL | 立即触发断言失败 | 内存调试标记匹配 | | Static MT + Dynamic MD | 堆损坏(Heap Corruption) | 内存管理器冲突 | > 📌 **关键原则**:微软官方禁止跨主版本混合(如VC2015与VC2019),仅允许同版本小更新(如14.0.24215→14.0.24234)[^4] --- ### 相关问题 1. 如何检测应用程序依赖的VC++运行库版本? 2. 静态链接(/MT)动态链接(/MD)各有何优缺点? 3. 如何解决跨模块传递STL对象时的ABI兼容性问题? 4. Windows清单文件(manifest)的详细配置方法? 5. 使用Dependency Walker分析DLL依赖项有哪些技巧?
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值