赞
踩
参考链接:https://blog.csdn.net/luoweifu/article/details/49055933
如下图所示,C++的工程里,C/C++ ----> Code Generation ----> Runtime LIbrary下一共有四个选项,这些代表什么意思呢
在这方面,经常看到以下名词:
这些名词看完这篇文章就知道了,后面会再解释
Runtime Library就是运行时的库,也就是程序运行的时候需要的库文件,说白了就是一堆.lib或.dll文件的集合(Windows平台下),在Windows下有lib和dll两种文件,比如C Runtime Library提供了相关的lib文件和dll文件,具体名字之后再讲
比如安装VS2010时,就会下载对应C++的CRT源码,放在安装目录VC\crt\src下,这个版本的CRT既包括老的C的库文件,也包括C++添加进去的新的库文件,所以VC\crt\src下,既有C的文件(如output.c、stdio.h等),也有C++的文件(如iostream、string)。
其实Runtime Library就是各自编程语言对应的库文件,比如说.NET Runtime、CRT,都是一样的,就是一堆库文件的集合
既然是库文件,那么使用库文件的时候,自然有两种使用的方法:
对于库文件,如果用的Static Linking,则是把对应的.lib库文件复制到自己的程序里,如果用的Dynamic Linking,则是把对应的.dll库文件load到自己的程序里,Static Linking更快,但需要拷贝库文件,Dynamic Linking虽然慢,但所有用库文件的程序都可以共享一份.dll文件
在C Runtime Library出来之前,对于库文件,都是用的.lib的Static Linking去做的,之后微软把库文件做成了.dll,让大家用Dynamic Linking进行使用。
主要两点:
不采用宽字符集的控制台程序的入口点为mainCRTStartup(void)。下面我们以该函数为例来分析运行时库究竟为我们添加了怎样的入口程序。这个函数在crt0.c中被定义,下列的代码经过了笔者的整理和简化:
void mainCRTStartup(void) { int mainret; /*获得WIN32完整的版本信息*/ _osver = GetVersion(); _winminor = (_osver >> 8) & 0x00FF ; _winmajor = _osver & 0x00FF ; _winver = (_winmajor << 8) + _winminor; _osver = (_osver >> 16) & 0x00FFFF ; _ioinit(); /* initialize lowio */ /* 获得命令行信息 */ _acmdln = (char *) GetCommandLineA(); /* 获得环境信息 */ _aenvptr = (char *) __crtGetEnvironmentStringsA(); _setargv(); /* 设置命令行参数 */ _setenvp(); /* 设置环境参数 */ _cinit(); /* C数据初始化:全局变量初始化,就在这里!*/ __initenv = _environ; // 可以看到,这里调用了常用的main函数,同时把命令行的参数作为main函数的参数传递了进去 mainret = main( __argc, __argv, _environ ); /*调用main函数*/ exit( mainret ); }
除了crt0.c外,C运行时库中还包含wcrt0.c、 wincrt0.c、wwincrt0.c三个文件用来提供初始化函数。wcrt0.c是crt0.c的宽字符集版,wincrt0.c中包含 windows应用程序的入口函数,而wwincrt0.c则是wincrt0.c的宽字符集版。
这些都是VS里的Runtime Library的选项,其实不同的选项就代表了不同版本的C/C++语言对应的库的版本,这些字母分别对应以下版本:
也就是说,/ML和/MLd是最早的C++的库文件,分别对应Release和Debug平台,而且此时的C++库文件不支持多线程,是使用Static Link的方式Link进对应的C++程序的,这意味着每一份程序,都会拷贝其代码整合进去。
接下来,C++提供了/MT和/MTd的库文件,此时的库文件就支持多线程了,同样是使用的Static Linking的方法Link进对应的C++程序的。
最后提供的/MD和MDd库文件,同样支持多线程,不过它使用的是Dynamic Link的方式,这样多个程序也能共享一份dll,使用的时候就把它加载到程序里,熟悉dll的人肯定知道,使用dll的时候需要对应的dll和对应的lib文件,lib文件用于快速找到dll中对应的函数位置。
概念解析
现在再来看看之前不懂的概念名词
三种模式的比较
(1). 静态链接的单线程库
静态链接的单线程库只能用于单线程的应用程序, C 运行时库的目标代码最终被编译在应用程序的二进制文件中。通过 /ML 编译选项可以设置 Visual C++ 使用静态链接的单线程库。
(2). 静态链接的多线程库
静态链接的多线程库的目标代码也最终被编译在应用程序的二进制文件中,但是它可以在多线程程序中使用。通过 /MT 编译选项可以设置 Visual C++ 使用静态链接的多线程库。
该选项生成的可执行文件运行时不需要运行时库dll的参加,会获得轻微的性能提升,但最终生成的二进制代码因链入庞大的运行时库实现而变得非常臃肿。当某项目以静态链接库的形式嵌入到多个项目,则可能造成运行时库的内存管理有多份,最终将导致致命的“Invalid Address specified to RtlValidateHeap”问题。
(3). 动态链接的运行时库
动态链接的运行时库将所有的 C 库函数保存在一个单独的动态链接库 MSVCRTxx.DLL 中, MSVCRTxx.DLL 处理了多线程问题。使用 /MD 编译选项可以设置 Visual C++ 使用动态。
链接时将按照传统VC链接dll的方式将运行时库MSVCRxx.DLL的导入库MSVCRT.lib链接,在运行时要求安装了相应版本的VC运行时库可再发行组件包(当然把这些运行时库dll放在应用程序目录下也是可以的)。 因/MD和/MDd方式不会将运行时库链接到可执行文件内部,可有效减少可执行文件尺寸。当多项目以MD方式运作时,其内部会采用同一个堆,内存管理将被简化,跨模块内存管理问题也能得到缓解。
/MDd 、 /MLd 或 /MTd 选项使用 Debug runtime library( 调试版本的运行时刻函数库 ) ,与 /MD 、 /ML 或 /MT 分别对应。 Debug 版本的 Runtime Library 包含了调试信息,并采用了一些保护机制以帮助发现错误,加强了对错误的检测,因此在运行性能方面比不上 Release 版本。
结论:/MD和/MDd将是潮流所趋,/ML和/MLd方式请及时放弃,/MT和/MTd在非必要时最好也不要采用了。
前面提到了,动态链接的运行时库,内部会采用同一个堆,下面两张图是/MT和/MD的内存模型:
之前在学习Hazel游戏引擎的时候遇到了这么一个问题,这里回顾一下:
我有一个Hazel.dll,游戏引擎作为dll,dll工程使用的Rumtime Library为/MT 。然后还有一个Application工程,工程里的Sandbox.cpp会使用Hazel.dll相关的内容,相关代码如下:
// Sandbox.cpp中 void OnEvent(Hazel::Event& e) override { std::string s = e.ToString(); } // Hazel.dll中的ToString函数如下 class HAZEL_API KeyPressedEvent : public KeyEvent { ... std::string ToString()const override { std::stringstream ss; ss << "KeyPressedEvent:\n KeyCode : " << m_Keycode << " KeyRepeated: " << m_KeyRepeated; return ss.str();//这里会new一个String并返回 } };
也就是说,下面的代码是这样的,然后执行这段代码会在}
后面报错:Sandbox.exe
void OnEvent(Hazel::Event& e) override
{
std::string s = e.ToString();// 由dll创建了一个string
}// 由Sandbox.exe负责销毁这个string,走到这里会报错
报错信息如下图所示:
创建string s
获取传递过来的s的地址
可以看到是在debug_heap.cpp报的错,说明与Heap有关,出现这个的原因是因为dll和exe分别拥有自己的Heap,导致的同一块内存在堆A上创建,又在堆B上释放
至于为什么是这样,可以分析一下上面问题的内存模型,如下图所示,由于Hazel.dll使用的/MT的模式,所以Hazel.dll和exe关于string的操作都是自己在自己特有的Heap上操作的,这个时候Sandbox.exe只能获取Hazel堆上的string s,但是不能在Sanbox.exe这边进行释放,因为这个任务必须交给Hazel.dll来执行:
所以解决办法有两个:
顺便提一下premake5.lua里如何设置相关的内容
参考链接:https://github.com/premake/premake-core/wiki/staticruntime
所以要使用/MD,就写staticruntime "off"
,要使用/MT,就用staticruntime "on"
也可以下面这样写,不过没有在工程前面就使用staticruntime好:
filter "configurations:Release"
defines "HZ_RELEASE"
buildoptions "/MD"
optimize "On"
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。