CRT库类型

6
我试图更好地了解Visual Studio 2013(C++->代码生成->运行时库)中的CRT库选项,以及如何知道选择哪个选项(以及何时更改默认选项)。
来自 MSDN
“可重用库及其所有用户应使用相同的CRT库类型,因此应使用相同的编译器开关。”
因此,我的理解是,如果您正在链接第三方库,则应使用与构建库所使用的相同的CRT版本。构建库的人应指定在构建中使用了什么CRT选项。
有没有一种方法可以通过查看.lib文件来确定使用了哪个CRT版本?
更重要的是,如果您没有链接任何第三方库,您将如何决定使用哪个选项?什么情况下会考虑更改默认选项?

你应该使用与构建库时相同的CRT版本。不一定 - 这取决于库接口的设计质量。如果它的设计方式使得CRT资源从未跨模块边界共享,那么两个模块使用不同的CRT版本就无关紧要了。例如,该库从不分配调用者后期需要释放的内存。设计一个可供任何调用者使用的库需要一定的纪律性。 - Igor Tandetnik
1个回答

9

简短回答:

因此,我的理解是,如果您正在链接第三方库,应使用构建库所使用的相同CRT版本。构建库的人应指定在构建中使用了什么CRT选项。

这是最不容易出错的选择。虽然可以混合运行时,但这样做可能会遇到意外错误。

是否有一种方法仅通过查看.lib文件来确定使用了哪个CRT版本?

我不知道单独的.lib,但如果第三方代码有DLL或EXE,则可以使用Windows Dependency Walker工具查看它依赖的CRT DLL。

如果它是静态库,并且您的代码的CRT选择不匹配,则在构建时会看到警告。

更重要的是,如果您没有链接任何第三方库,您将如何决定使用哪个选项?什么情况下考虑更改默认设置?

对于最简单的部署方式,静态链接是最好的选择;您只需将可执行文件单独打包即可运行。对于较大的项目,在您有多个EXE和DLL的情况下,如果您进行静态链接,则代码大小会更大。如果在同一进程中有多个模块(EXE加上一个或多个自己的DLL),最好共享相同的CRT代码。

库变体

有四个C/C++运行时库的变体可供您构建代码:

  • 多线程调试DLL
  • 多线程DLL
  • 多线程调试
  • 多线程

您可以通过在Visual Studio中右键单击项目并选择属性,点击弹出对话框中的C/C++下的代码生成选项,并转到运行时库属性来选择要使用的库。

Visual Studio Code Generation Properties

请记住,此设置是每个配置独立的,因为您需要选择一个Debug运行库用于Debug配置,以及Release运行库用于Release配置。
有什么区别? DLL运行库选项意味着您动态链接到C/C++运行时,为了使您的程序运行,该DLL需要在您的程序可以找到它的地方(稍后会详细介绍)。
未提及DLL的选项(Multi-threaded DebugMulti-threaded Release)会导致您的程序静态链接到运行时。这意味着您不需要外部DLL来运行程序,但由于额外的代码,您的程序会变大,并且还有其他原因可能不想选择它。
默认情况下,在Visual Studio中创建新项目时,它将使用DLL运行时。
多线程运行时名称的使用是一个遗留问题,当时存在非线程安全和多线程的C/C++运行时。即使您的应用程序是单线程的,现代Visual Studio始终会使用多线程运行时。
部署DLL运行时:
如果您链接到DLL运行时,则需要考虑在发布程序(测试和向客户)时如何部署它们。
如果将Debug版本提供给测试团队,请确保您也提供了Debug变体的DLL运行时。
此外,不要忘记获取正确的架构(例如x86 vs x64)。
可再发行安装程序。
微软提供可重新分发的包,安装发布版(但不包括调试版)DLL文件。你可以通过搜索类似于Visual C++ Redistributable 2013(将Visual Studio版本替换为你需要的)来轻松找到它们。你也可以直接访问微软网站上的Latest Supported Visual C++ Downloads
这些可重新分发的包是可执行文件,你可以从程序安装程序中调用它们(或者手动运行它们以设置测试环境)。请注意,x86和x64有单独的可重新分发的包。

合并模块

如果您正在构建MSI安装程序,您可以将所使用的C/C++运行时合并模块合并到您的安装程序包中。这些合并模块通常位于C:\Program Files (x86)\Common Files\Merge Modules中。例如,Visual Studio 2013 x86 C/C++运行时的合并模块称为Microsoft_VC120_CRT_x86.msm
请注意,这些合并模块名称中的版本号不是基于年份的产品版本,而是内部版本号。此维基百科上的表格显示了版本号之间的映射,以避免混淆。
与上面的独立可执行可再发行安装程序不同,合并模块也有调试变体。
一些版本的Visual Studio支持“Visual Studio Installer”项目类型(在“其他项目类型”中的“设置和部署”类别下),如果您将程序项目的输出包含在其中一个安装程序中,则运行时合并模块将自动包含在内。您还可以使用这个技巧来获得一个安装程序,以安装内部测试目的的“Debug”运行时(任何虚拟的C/C++项目都可以,它不必实际安装您的程序)。

从Redist文件夹复制

您也可以将Visual C++安装中的redist文件夹中的DLL文件复制到您的程序安装位置。例如,对于Visual Studio 2013,您将在类似于C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\redist\x64\Microsoft.VC120.CRT\的地方找到x64 C/C++运行时库。
调试变体可以在类似于C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\redist\Debug_NonRedist的地方找到。
(根据您的Visual Studio版本和安装位置适当调整路径)。

混合C/C++运行时

在理想情况下,您应该对程序中所有链接的库使用相同版本的Visual Studio和相同的C/C++运行时库变体(Debug vs ReleaseDLL vs 静态链接)。
如果您混合使用不同版本的运行时库,则可能会出现链接错误,程序可能完全无法运行,或者更糟糕的是,它似乎可以工作,但在某些情况下会崩溃或给出错误结果。
忽略默认库
常见情况是,您只有某个第三方库的Release版本,但仍希望能够使用该库构建自己代码的Debug变体。另一种情况是,您有一个使用静态C/C++运行时的库,并且希望在程序中使用DLL版本,或反之亦然。
如果第三方库是C代码,则您可能可以通过使用/NODEFAULTLIB链接器选项来实现这一点。
如果库是C++代码,那么你可能没有希望,因为生成的代码太多与特定运行时中的符号绑定在一起。
以下是不同的库名称:
LIBCMT.LIB:静态链接发布运行时(也称为多线程)。 LIBCMTD.LIB:静态链接调试运行时(也称为多线程调试)。 MSVCRT.LIB:动态链接发布运行时(也称为多线程DL)。 MSVCRTD.LIB:动态链接调试运行时(也称为多线程调试DLL)。
请记住,您要忽略的运行时库是第三方代码正在使用的库,即它将与您自己的程序使用的库不同。如果查看构建输出,您将被提示选择正确的库,例如:
LINK:警告LNK4098:defaultlib“LIBCMTD”与其他库的使用冲突;使用/NODEFAULTLIB:library
你可以通过右键单击项目,选择属性,点击链接器下的输入条目,并将运行时库名称添加到该条目中来指定要忽略的库。

Ignoring Default Libs in Visual Studio

跨模块边界

在模块边界(例如在一个EXE和它所加载的DLL之间)中,您还可能以更微妙的方式遇到C/C++运行时不匹配的问题。

例如,C库中的数据结构可能因不同的运行时而定义不同。我曾见过这导致程序崩溃,原因是使用了一个API中的FILE*的DLL。文件在一个模块中使用一个C运行时打开,并被另一个具有不同、不兼容实现的模块所使用。更安全的选择将包括传递Windows API的HANDLE对象等,或以一种运行时无关的方式封装文件交互。

不同的运行时还使用它们自己的内存堆进行分配。如果对象在一个堆上分配,在另一个模块上释放,那么如果C/C++运行时不匹配,就很可能会发生崩溃。当然,这仅适用于使用C或C++运行时进行的分配,例如mallocnew。如果在任何地方都使用默认的Windows进程堆,那么一切都没问题。

请注意,如果模块静态链接到 C/C++ 运行时库,即使它们链接到相同的运行时变体,也会出现这个问题,因为仍然会有两个不同的运行时在运行并拥有自己的内存堆。因此,在这种情况下,您需要使用 DLL C/C++ 运行时。

如果跨模块边界存在运行时实现功能,如异常或带有 vtable 的类,则最好在每个模块中使用(相同版本的)DLL 运行时,尽管某些不匹配的组合在实践中也可以工作。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接