夏天/isummer

Sun of my life !Talk is cheap, Show me the code! 追风赶月莫停留,平芜尽处是春山~

博客园 首页 新随笔 联系 管理

 

先说结论:

  (1)程序在release下可以正常运行,但是在debug下报错,错误原因: debug_heap.cpp Line:980 Expression:__acrt_first_block==header"

  原来debug和release都是采用MD的方式配置运行时库。

  解决方式:将debug的运行时库的使用方式配置为:MDd, 重新编辑,则没有问题。 release则保持默认MD的方式不变。

 

 1. “修改了当前程序的vc运行库配置,问题解决

(1)修改:项目-属性-配置属性-C/C++-代码生成-运行库,将其改为“多线程调试(/MTd)”。”

  其中:

  • 【多线程/MT】
  • 【多线程调试/MTd】-----
  • 【多线程DLL/MD(默认)】-------
  • 【多线程调试DLL/MDd】

 

  

/MT、/MTd、/MD、/MDd什么意思:

    MT和MD是编译选项中的两个重要概念,它们分别代表不同的C/C++运行时库的链接方式。了解并正确选择这些选项对于开发高效、稳定的应用程序至关重要。

  MT选项:静态链接多线程库方式。选择此选项时,编译器会从运行时库中选择多线程静态连接库解释程序中的代码,具体链接的是LIBCMT.lib库。

    (1)特点:

      • 静态链接:C/C++运行时库代码会被直接嵌入到生成的可执行文件或库文件中。
      • 独立运行:生成的可执行文件不依赖外部的C运行时DLL(如msvcrt.dll),因此在没有外部DLL的情况下也能运行。
      • 文件较大:由于链入了庞大的运行时库实现,最终生成的二进制文件可能会较大

    (2)应用场景:

      

      • 当需要在没有安装Visual Studio或相应C运行时DLL的环境中运行程序时,可以在任何设备主机上运行,优选。
      • 对程序性能有较高要求,且可以接受较大的二进制文件时。

  MD选项:即多线程动态库。选择此选项时,程序在运行时依赖于C运行时DLL(如msvcrt.dll),具体链接的是MSVCRT.lib导入库,对应动态库为MSVCRT.dll

    (1)特点:

      • 动态链接:程序在运行时动态加载对应的DLL。
      • 文件较小:由于符号链接都是通过动态库的加载来匹配获取,生成的EXE文件相对较小。
      • 依赖外部DLL:需要在目标系统上确保这些DLL存在,否则程序无法运行。

    (2)应用场景:

        

      • 当目标系统已经安装了相应的C运行时DLL时。
      • 希望减小生成的EXE文件大小时。

  对比:

  • 独立性 vs 依赖性:MT选项生成的程序独立性强,不依赖外部DLL;而MD选项生成的程序则依赖于外部DLL。
  • 文件大小:MT选项生成的二进制文件较大,而MD选项生成的二进制文件较小。
  • 性能:由于静态链接减少了运行时动态加载的开销,MT选项可能在某些情况下获得轻微的性能提升。但这也取决于具体的应用场景和硬件配置。
  • 可维护性:MD选项由于依赖于外部DLL,当DLL更新时,程序可能无需重新编译即可获得新的功能或修复。而MT选项则需要在每次更新运行时库时重新编译程序。

   

  除了MT和MD之外,还有它们的调试版本:MTd(Multi-threaded Debug Static)和MDd(Multi-threaded Debug DLL)。这些调试版本包含了额外的调试信息,并在运行时执行更多的检查,有助于在开发阶段发现内存泄漏和其他运行时错误(言外之意,有些错误d调试版本可以检测处理啊,而非调试版本链接运行时库无法检测)。但需要注意的是,调试版本的二进制文件较大,且性能不如发布版好。 

   总结:选择MT还是MD选项取决于具体的应用场景和需求。如果需要生成独立的、不依赖外部DLL的程序,并且可以接受较大的二进制文件,那么MT选项是合适的选择。而如果希望减小生成的EXE文件大小,并且目标系统已经安装了相应的C运行时DLL,那么MD选项则更为合适。同时,在开发阶段应使用相应的调试版本(MTd或MDd)来确保程序的稳定性和正确性。

 

  简而言之:

  1. 带T的表示:LIB版本,即静态链接运行库,编译后“运行库也添加到exe可执行文件中,exe文件会变得很大。如果项目exe依赖多个模块,则会出现重复定义的情况。

   2. 带D的表示:DLL版本,即动态链接运行库,编译后,exe文件只在运行时依赖到才将运行时库dll加载进来,生成的exe文件会很小。

  3. 带d的表示:对应的调试版本,会附加各种调试信息,便于调试软件。

  4. MT和MTd表示采用多线程CRT库的静态LIB版本。

    (1)【编译方式】该选项会在编译器将运行时库以静态LIB的形式完全嵌入到exe中。由于依赖的运行时库已经嵌入到exe中,所以exe可以直接运行。

    (2) 【exe文件大小和性能】该选项生成的可知那个文件在运行时不需要加载DLL,则会获得轻微的性能提升,但是最终生成的二进制文件会变得庞大而臃肿。

    (3)【编译影响】当应用项目以“静态链接库”的形式嵌入到多个项目中,则可能造成运行时库的内存管理有多分,会导致致命的“InvalidAddress specificed to RtlValidate

Heap”问题。

 

 

 

使用心得:

  1. 如果项目A编译,并依赖项目B的ProjectB.dll库,则要保证二者的运行时库一致,否则出现错误:RuntimeLibray不匹配。可以根据错误类型,进一步判断运行时库的差异进行调试。

  比如:Qt的不同版本以及对应不同平台,本质上是不同运行时库编译的结果不同,因此:不同平台编译的qt模块程序不互用。如:用Vs2019编译后的qt,运行时库设置为MDd(即:多线程DLL版本的运行库的调试版本),调用ProjectB的静态库的编译运行时库为MTd(多线程Lib版本运行时库的可调试版本),则二者不匹配,因此需要设置为一致。

 

 

 

  2. MD和MDd表示采用多线程CRT库的动态DLL版本。

 

    (1)【编译方式】链接时,将按照传统VC链接dll的方式,只需要将运行时库MSVCRXXX.dll对应的导入静态库MSVCRT.lib链接到项目exe中即可,从而减少了项目exe的体积大小,但是:在运行时,需要将特定依赖的运行时库msvcrxxxx.dll都拷贝exe所在目录即可,否则程序无法运行。后者安装微软提供的运行时环境的exe将运行时库安装到目标主机上。

 

    (2)由于MD和MDd方式不会将运行时库装入exe内部,可有效减小exe文件体积。当多个项目以“MD”方式运行时,内部会采用同一个堆,内存管理将被简化,跨模块的内存管理问题也将得到环节。

  静态运行时库:LIBCMT.lib
  动态运行时库:MSVCRT.lib + MSVCR80.DLL

  3. MD和MDd是潮流趋势,而MT和MTd的方式非必要时,尽量不采用。

 

举个例子:来看一个例子,编译一个静态库和一个动态库,均实现两个整数相加的功能

(1)以下代码:运行时库采用MTd(多线程LIB运行时库链接)的方式,编程成静态库ProjectAdd.lib给第三方使用。

 
// adds.h
// add后面加个s代表静态库


#pragma once
int add(int,int);
// adds.cpp
// 静态库
#include "adds.h"
int add(int a, int b)
{
    return a+b;
}

(2)以下代码:运行时库采用MTd(多线程LIB运行时库链接)的方式,编译成ProjectAdd.lib和ProjectAdd.dll库给第三方以DLL方式使用。

// addd.h
// add后面加d代表动态库
#pragma once
#ifndef MYLIB_API
#define MYLIB_API _declspec(dllexport)
#endif
 
MYLIB_API int  add(int,int);
// addd.cpp
// 动态库
#include "addd.h"
int add(int a, int b)
{
  return a+b;
}

(2)测试程序如下:注意,同样要采用MTd方式配置运行时库。

// test.cpp
// 测试程序
#include <iostream>
 
// 测试静态库,此处为1,测试动态库,此处为0
#define TEST_STATIC_LINK 1
 
#if TEST_STATIC_LINK 
  #include <adds.h>
#else
  #define MYLIB_API _declspec(dllimport)
  #include "addd.h"
#endif
 
using namespace std;
 
int main()
{
    cout << add(2,3) << endl;
    return 0;
}

 

项目编译(1):测试程序编译必须选用MTd方式来配置运行时库,并:以静态库的方式使用ProjectAdd.lib库时,生成可执行文件。ProjectCC.exe,则拷贝到第三方机器上,可以运行。因为测试程序ProjectCC.exe和依赖的库ProjectAdd.lib都是静态运行时库;

项目编译(2):测试程序编译必须选用MTd方式来配置运行时库,并:以DLL方式加载依赖的ProjectAdd库时,生成的可执行文件。Project.exe,需要把依赖的库ProjectAdd.dll同样拷贝到可执行文件目录,即可成功运行。

  以上说明:ProjectCC.exe和ProjectAdd库在配置运行时库时,都要保持一致的运行时库的链接方式,都必须配置为MTd的方式。否则,出现比那一链接错误。即一个程序中混合了不同的运行时库(静态库和动态库,调试库和非调试库),可能会产生冲突,所以一个程序中应该使用相同的运行时库

1>正在链接...
 
1>MSVCRTD.lib(ti_inst.obj) : error LNK2005: "private: __thiscall type_info::type_info(class type_info const &)" (??0type_info@@AAE@ABV0@@Z) 已经在LIBCMTD.lib(typinfo.obj) 中定义
 
1>MSVCRTD.lib(ti_inst.obj) : error LNK2005: "private: class type_info & __thiscall type_info::operator=(class type_info const &)" (??4type_info@@AAEAAV0@ABV0@@Z) 已经在LIBCMTD.lib(typinfo.obj) 中定义

 

 

以下代码:运行时库采用MTd的方式,编译成ProjectAdd.lib和ProjectAdd.dll库给第三方以DLL方式使用。

 

 

 

 

 

 

 

 

 

 

 

  四个选项又是什么意思,有什么区别?接下来详细讨论。
    先奉上一个写得非常详细的博客链接:你所不知道的C和C++运行库,这个文章中写得非常详细,本文也主要参考了这篇文章。
    还有这篇C Runtime Library 的来历

(1)VS项目属性中的C/C++运行库:你不知道的C和C++运行库。

  为了提高C语言的开发效率,C标准定义了一系列常用的函数,称为C库函数。C标准仅仅定义了函数原型,并没有提供实现。因此这个任务留给了各个支持C语言标准的编译器每个编译器通常实现了标准C的超集,称为C运行时库(C Run Time Libray) ,简称CRT。对于VC++编译器来说,它提供的CRT库支持C标准定义的标准C函数,同时也有一些专门针对windows系统特别设计的函数。
  与C语言类似,C++也定义了自己的标准,同时提供相关支持库,我们把它称为C++运行时库或C++标准库。
  由于C++对C的兼容性,C++标准库包括了C标准库,除此之外还包括IO流和标准模板库STL。

 

  VC++在何处实现C和C++运行库:对应的MSVC编译器。

  VC++完美的支持C和C++标准,因此也就按照C和C++的标准定义的函数原型实现了上述运行时库。为了方便有不同需求的客户的使用,VC++分别实现了动态链接库DLL版本和静态库LIB版本。同时为了支持程序调试且不影响程序的性能,又分别提供了对应的调试版本。调试版本的名称在Release版本名称后添了字母d。

  对于C运行时库CRT,VC6.0、VC2005、VC2008和VC2010均提供了DLL版本和LIB版本。

  (1)上述各个编译器提供的LIB版的CRT库,均实现在libcmt.lib。对应的调试版名称为libcmtd.lib

  (2)上述各个编译器提供的DLL版本名称根据编译器不同而不同,我们可以从名称上加以分辨。
  VC6.使用的CRT库的DLL版本在MSVCRT.DLL中实现, 对应调试版本为MSVCRTD.DLL。
  VC2005使用的CRT库的DLL版本在MSVCR80.DLL中实现,对应调试版本为MSVCR80.DLL。
  VC2008使用的CRT库的DLL版本在MSVCR90.DLL中实现,对应调试版本为MSVCR90D.DLL。
  VC2010使用的CRT库的DLL版本在MSVCR100.DLL中实现,对应调试版本为MSVCR100D.DLL。

  典型的dll:msvcr2019.dll以及调试版本msvcr2019d.dll

 

  C++标准兼容C标准,但VC各版本将C++编译器使用的C标准库与C编译器使用的C运行库一起实现,它们使用相同的运行库。

  对于C++标准库中的IO流和STL,VC6.0、VC2005、VC2008和VC2010也提供了DLL版本和LIB版本。
  (1)LIB版均实现在libcpmt.lib中,对应的调试版本为libcpmtd.lib。

  不同版本的编译器实现的DLL也不相同。
  VC6.使用的C++类库的 DLL版本在MSVCP60.DLL中实现, 对应调试版本为MSVCP60D.LIB。
  VC2005使用的C++类库的DLL版本在MSVCP80.DLL中实现,对应调试版本为MSVCP80.DLL。
  VC2008使用的C++类库的 DLL版本在MSVCP90.DLL中实现,对应调试版本为MSVCP90D.DLL。
  VC2010使用的C++类库的DLL版本在MSVCP100.DLL中实现,对应调试版本为MSVCP100D.DLL。

  典型的:msvcp2019.dll和msvcp2019.dll库,其中:c:c运行库, p:对应CplusplusC++运行库。

  

  在各个版本的编译器中,我们可以通过配置选项来设置程序使用的C和C++运行时库的类型。

  

     MT选项(多线程Lib版本):链接LIB版的C和C++运行库。在链接时就会在将C和C++运行时库集成到程序中成为程序中的代码,程序体积会变大
  MTd选项(多线程Lib调试版本):链接LIB的C和C++运行库对应的调试版,同样程序的体积会变大
  MD选项(多线程DLL版本):使用DLL版的C和C++运行库,这样在程序运行时会动态的加载对应的DLL,程序体积会减小,缺点是在系统没有对应DLL时程序无法运行。
  MDd选项(多线程DLL调试版本):表示使用DLL版本的C和C++运行库对应的调试版

  在LeakDialog内存泄漏实验文章:https://blog.csdn.net/ithzhang/article/details/19086139 使用VC6.0的默认配置没有拦截到内存泄露。其原因是VC6.0的控制台项目默认配置是静态链接CRT库(单线程版,后面会介绍)

  

  动态版(DLL)和静态版(LIB)C和C++运行库的优缺点

  (1)因为静态版必须把C和C++运行库复制到目标程序中,所以产生的可执行文件会比较大。同时对于使用多个模块的大型软件来说,如果每个模块均选择静态链接C或C++运行库,在程序运行时就会存在多个运行库。在链接时也会出现重复定义的问题,如下 图所示。

  

  (2)使用DLL版的C和C++运行库,程序在运行时动态的加载对应的DLL程序体积变小,但一个很大的问题就是一旦找不到对应DLL,程序将无法运行。假设使用VC6.0并选择使用MD(DLL版本链接方式)选项构建生成应用ProjectA.dll,那么当用户使用VC2005来使用这个ProjectA.dll时很可能出现找不到MSVCRT.DLL或MSVCP60.DLL的情况(因为后面的dll是编译ProjectA.dll库是依赖的)

  在这里介绍一个很好的工具:Dependency Walker,可以用来分析DLL的依赖关系,同时查看DLL导出的函数

  打开:使用该工具打开MSVCRT.DLL可以看到:经常使用使用的C函数,如printf ,getchar,malloc等

   打开MSVCP100.DLL,也可以找到这些C函数,如下图:

 

  在开发的过程中我们也会遇到如下图的链接错误,LIBCD.lib究竟是何方神圣呢?

  它其实是LIBC.lib的调试版,而LIBC.lib是只有在VC6.0才会使用的静态库,该库是CRT的单线程版,用于支持单线程版本的CRT。VC2005等更高版本的编译器已经不再提供单线程版本,转而使用多线程版的MSVCR80.DLL或libcmt.lib。

  当遇到上述符号定义冲突的链接错误时,可以选择忽略libcd.lib。

 

  

C Runtime Library 的来历

  参考:https://blog.csdn.net/nodeathphoenix/article/details/8288233

  msvcrt.dll (名称:MicroSoft C Runtime Library,MSVCRT)提供了printf,malloc,strcpy等C语言库函数,并且为使用C/C++(Vc)编绎的程序提供了初始化(如获取命令行参数)以及退出等功能.

  标准C,Windows API,MFC,STL。这些都是预先编写好的库,实际开发时选哪个,取决于项目的规模和性质、程序的风格,还有个人的喜好。

  1. CRT

   运行时库就是C Run-Time Library,是C而非C++语言世界的概念。取这个名字就是因为你的C程序运行时需要这些库中的函数。

  C语言是所谓的“小内核”语言,就其语言本身来说很小(不多的关键字,程序流程控制,数据类型等);所以,C语言内核开发出来之后, Dennis Ritchie 和 Brian Kernighan 就用C本身重写了 90% 以上的 UNIX 系统函数,并且把其中最常用的部分独立出来,形成头文件和对应的 LIBRARY,C run-time library 就是这样形成的。

  随后,随着C语言的流行,各个C编译器的生产商/个体/团体都遵循老的传统,在不同平台上都有相对应的 Standard Library,但大部分实现都是与各个平台有关的。由于各个C编译器对C的支持和理解有很多分歧和微妙的差别,所以就有了ANSI C;ANSI C(主观意图上)详细的规定了 C 语言各个要素的具体含义和编译器实现要求,引进了新的函数声明方式,同时订立了 Standard Library 的标准形式。所以C运行时库由编译器生产商提供。至于由其他厂商/个人/团体提供的头文件和库函数,应当称为第三方 C 运行库(Third party C run-time libraries)。

 

  2. Standard C++ Library

  到了 C++ 世界里,有另外一个概念:Standard C++ Library,它包括了上面所说的C Run-Time LibrarySTL。包含C Run-Time Library的原因很明显,C++ 是 C 的超集,没有理由再重新来一个 C ++ run-time library。

  VC针对C++ 加入的Standard C++ Library主要包括:LIBCP.LIB, LIBCPMT.LIB和 MSVCPRT.LIB。

 

二、Microsoft的C\C++

  CRT原先是指Microsoft开发的C Runtime Library,用于操作系统的开发及运行。后来在此基础上开发了C++ Runtime Library,所以现在CRT是指Microsoft开发的C/C++ Runtime Library。在VC的CRT/SRC目录下,可以看到CRT的源码,不仅有C的,也有C++的。

  CRT原先的目的就是支持操作系统的运行。因为Windows操作系统除汇编部分外,都是用C/C++编写的,所以内核及许多关键服务都在CRT上运行(它们都采用dll技术动态链接)。此外,用 VC编写的C/C++程序也用到它们(可以动态链接,也可以静态链接,前者运行时需要系统中已安装CRT的dll,后者不需要)。

  可以说,CRT就是 Microsoft编写Windows时使用的低层类库。然后,它又被当作C++标准库的一个实现包含在了VC系列中;我们在VC中使用的C++标准库,其实就是CRT的一个真子集(少了C++标准所不包含的代码,特别是大量的低层C代码)  

  C++标准,是C++的通用语言规范,指导所有C ++使用者。而CRT的其中一部分可以看作是Microsoft开发的一个C++标准库实现(其实也确实如此,Microsoft在开发CRT时,参考了正在标准化过程中的C++语言规范)。它与C++标准有一定的差距,部分原因是,在C++没有完成标准化之前,CRT已经开发并投入使用了。为了向下兼容以前的Windows代码,早期的CRT与C++标准总有一定的差距。但是CRT确实在不断的改进中。VC6带的CRT与C++标准还有比较大的差距,而 VC8的几乎完全符合C++标准了。

 

2. Windows中的CRT与Windows API

  至于CRT与WINDOWS API的关系,与许多人理解的相反,WINDOWS API作为Windows的一部份,是在CRT的基础上开发的。你可以将Windows(及其API)看作一个项目,而这个项目使用的语言是汇编/C/C ++,使用的类库就是CRT。所以,离开CRT,Windows API也无法使用的。

  在编写操作系统时,你需要一个合适的低层库,以便完成一些基本的、多次重复的工作。于是,就有了CRT。在最低层的时候,根本连DLL这个概念都没有的,所以CRT的源代码只能做成lib,被静态链接。然后,随着Windows越做越复杂,Microsoft提出了API的概念,它提供Windows开发者一组接口,可以直接操作Windows,这就是Windows API了。当然,Windows API也是在CRT之上编写的。

  接着,Microsoft想给予C/C++程序员以足够的支持,除了原始CRT之外,还要增加在Windows平台上编程所特有的东西,如thread等等。这些东西都是和平台相关的,只能建立在Windows API上。而这些新增内容,也被放进了CRT中。此时,CRT不仅仅包含最低层平台无关的代码,还包括平台相关的部分。如你调用CRT的 _beginthread,其实内部调用了Windows API的CreateThread。加入这些东西后,CRT仍然被用作编写操作系统;但是显然,那些调用了Windows API的部分已经失去移值性了

  然后,CRT被封装成产品,随编译器一起发布。此时CRT产品的LIB和DLL都是Windows格式的,你不能在Windows以外的平台上使用EXE或DLL吧,这就是CRT和CRT产品的区别。Windows API的产品,或是Windows的其他许多组成部分也是一些LIB/DLL文件,这些都是表面的东西,是与Windows绑定在一起的。

  当然,CRT的一些组成部分也调用了Windows API。这可能就是有人认为CRT是建立的Windows API基础上的原因。但是实际上,这一部分剥离CRT没有任何的问题。只不过Microsoft将在Windows平台上可以使用的C/C++低层库都加入到CRT中。因此,CRT中很大一部分是操作系统平台无关的(原始的CRT),是开发Windows本身及其上一切的基础。它们也可以作为一个C/C+ +库在其他操作系统平台上使用。还有一部分,则是和Windows紧密绑定的,调用Windows API来实现的,可以看作扩展的CRT。之所以将这两部分放在一起,是因为它们都是开发Windows操作系统所需要的,也因为它们也都是Windows 平台上的C/C++程序员所需要的。这种复杂关系是Microsoft的人为因素造成的,不能因此认为CRT是建立在Windows或Windows API基础上的。

  综上,CRT(Microsoft's C/C++ Runtime Library)的一个真子集(主要是C++ Runtime Library)是一个符合(或至少是企图符合)C++标准的C++库。而Windows API(以及Windows的其他许多部分)都是在CRT的基础上开发的。

 

  1)C 运行时库就是 C run-time library(C运行时库),是 C 而非 C++ 语言世界的概念:取这个名字就是因为你的 C 程序运行时需要这些库中的函数.

  2)到了 C++ 世界里,有另外一个概念:Standard C++ Library(标准C++库),它包括了上面所说的 C run- time library 和 STL。

  Windows环境下,VC提供的 C run-time library又分为动态运行时库和静态运行时库。
  (1)动态运行时库主要是DLL库文件

    msvcrt.dll(DLL版本,动态运行时库) 

    msvcrtd.dll(DLL版本, 动态运行时库,调试版本)

  (2)静态运行时库(release版)对应的主要文件是:

    LIBC.LIB (LIB静态库版本,Single thread static library,单线程版本)

    LIBCMT.LIB (LIB静态库版本,Multithread static library,多线程版本)

  msvcrt.dll提供几千个C函数,即使是像printf这么低级的函数都在msvcrt.dll里。其实你的程序运行时,很大一部分时间时在这些运行库里运行。在你的程序(release版)被编译时,VC会根据你的编译选项(单线程、多线程或DLL)自动将相应的运行时库文件       

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

参考:  

  https://learn.microsoft.com/en-us/answers/questions/240332/assert-when-using-mtd-dynamic-library

   https://learn.microsoft.com/en-us/cpp/build/reference/md-mt-ld-use-run-time-library?view=msvc-170

参考博客:https://blog.csdn.net/cnhk1225/article/details/78330322

posted on 2024-12-16 17:25  夏天/isummer  阅读(15)  评论(0编辑  收藏  举报