我如何决定是否拥有多个 VC++ CRT 状态对我的应用程序来说是个问题吗?
这篇 MSDN 文章 说,如果我的应用程序加载多次使用 VC++ 运行时,因为它或它所依赖的某些 DLL 静态链接到 VC++ 运行时,然后应用程序将具有多个 CRT 状态,这可能会导致未定义的行为。
我究竟如何确定这对我来说是否是一个问题?例如这篇 MSDN 文章中的几个示例是前提是基本上说由 C++ 运行时维护的对象(例如文件句柄)不应跨 DLL 边界传递。如果我希望我的项目静态链接到 VC++ 运行时,需要检查的事项列表到底是什么?
This MSDN article says that if my application loads VC++ runtime multiple times because either it or some DLLs it depends on are statically linked against VC++ runtime then the application will have multiple CRT states and this can lead to undefined behaviour.
How exactly do I decide if this is a problem for me? For example in this MSDN article several examples are provided that basically say that objects maintained by C++ runtime such as file handles should ot be passed across DLL boundaries. What exactly is a list of things to check if I want my project to statically link against VC++ runtime?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(7)
只要您不执行某些操作,就可以拥有 CRT 的多个副本...:
CRT 的每个副本都将管理自己的堆。
如果您使用“new”在模块 A 中分配基于堆的对象,然后将其传递到模块 B,并在模块 B 中尝试使用“delete”释放它,则可能会导致意外问题。模块 B 的 CRT 将尝试根据其自己的堆来识别指针,这就是未定义行为出现的地方:如果幸运的话,模块 B 中的 CRT 将检测到问题,并且您将得到堆损坏错误。如果你运气不好,会发生一些奇怪的事情,而且你直到很久以后才会注意到它......
如果你在模块之间传递其他 CRT 管理的东西(例如文件句柄),你也会遇到严重的问题。
如果您的所有模块都动态链接到CRT,那么它们都将共享同一个堆,并且您可以传递内容而不必担心它们在哪里被删除。
如果您将 CRT 静态链接到每个模块,那么您必须注意您的接口不包含这些类型,并且您不要在一个模块中使用“new”或 malloc 进行分配,然后尝试在其他。
您可以通过使用操作系统分配函数(例如 MS Windows 的 GlobalAlloc()/GlobalFree())来绕过此限制,但这可能会很痛苦,因为您必须跟踪每个指针使用的分配方案。
必须担心目标机器是否具有您的模块所需的 CRT DLL,或者打包一个与您的应用程序一起使用可能会很痛苦,但这是一次性的痛苦 - 一旦完成,您就无需担心上面所有其他的东西。
It's OK to have multiple copies of the CRT as long as you aren't doing certain things...:
Each copy of the CRT will manage its own heap.
This can cause unexpected problems if you allocate a heap-based object in module A using 'new', then pass it to module B where you attempt to release it using 'delete'. The CRT for module B will attempt to identify the pointer in terms of its own heap, and that's where the undefined behavior come in: If you're lucky, the CRT in module B will detect the problem and you'll get a heap corruption error. If you're unlucky, something weird will happen, and you won't notice it until much later...
You'll also have serious problems if you pass other CRT-managed things like file handles between modules.
If all of your modules dynamically link to the CRT, then they'll all share the same heap, and you can pass things around and not have to worry about where they get deleted.
If you statically link the CRT into each module, then you have to take care that your interfaces don't include these types, and that you don't allocate using 'new' or malloc in one module, and then attempt to clean up in another.
You can get around this limitation by using an OS allocation function like MS Windows's GlobalAlloc()/GlobalFree(), but this can be a pain since you have to keep track of which allocation scheme you used for each pointer.
Having to worry about whether the target machine has the CRT DLL your modules require, or packaging one to go along with your application can be a pain, but it's a one-time pain - and once done, you won't need to worry about all that other stuff above.
这并不是说您不能传递 CRT 句柄。当你有两个模块读/写句柄时你应该小心。
例如,在dll A中,您编写
并在主程序中编写
当dll A和您的主程序有两个CRT时,新的删除操作将发生在不同的堆中。 DLL中的堆无法管理主程序中的堆,从而导致内存泄漏。
文件句柄上也是如此。除了内存资源之外,内部文件句柄可能有不同的实现。在一个模块中调用 fopen 并在另一个模块中调用 fwrite 和 fclose 可能会导致问题,因为 CRT 运行时中的数据 FILE* 点可能不同。
但是您当然可以在两个模块之间来回传递 FILE* 或内存指针。
It is not that you can't pass CRT handles around. It is that you should be careful when you have two modules reading/writing the handle.
For example, in dll A, you write
and in the main program, write
When dll A and your main program have two CRTs, the new an delete operations will happen in different heaps. The heap in the DLL can't manage the heap in the main program, resulting memory leaks.
Same thing on File handle. Internally File handle may have different implementations in addition to its memory resource. Calling fopen in one module and calling fwrite and fclose in another one could result problems as the data FILE* points could be different in CRT runtimes.
But you can certainly pass FILE* or memory pointer back and forth between two modules.
我在加载不同版本的 MSVC 运行时时遇到的一个问题是,每个运行时都有自己的堆,因此即使有足够的可用内存,您最终也可能会因“内存不足”而导致分配失败,但在错误的堆。
One issue I've seen with having different versions of the MSVC runtime loaded is that each runtime has its own heap, so you can end up with allocations failing with "out of memory" even though there is plenty of memory available, but in the wrong heap.
这样做的主要问题是这些问题可能在运行时发生。在二进制级别,DLL 进行函数调用,最终调用操作系统。调用本身很好,但在运行时参数却不好。当然,这可能取决于所使用的确切代码路径。
更糟糕的是,它甚至可能依赖于时间或机器。一个例子是共享资源,由两个 DLL 使用并受到适当的引用计数的保护。最后一个用户将删除它,这可能是创建它的 DLL(幸运)或其他(麻烦)
The major problem with this is that the problems can occur at runtime. At the binary level, you've got DLLs making function calls, eventually to the OS. The calls themselves are fine, but at runtime the arguments are not. Of course, this can depend on the exact code paths exercised.
Worse, it could even be time- or machine-dependent. An example of that would be a shared resource, used by two DLLs and protected by a proper reference count. The last user will delete it, which could be the DLL that created it (lucky) or the other (trouble)
据我所知,没有一种(安全)方法来检查库与哪个版本的 CRT 静态链接。
不过,您可以使用 DependencyWalker 等程序检查二进制文件。如果您在依赖项列表中没有看到任何以 MSVC 开头的 DLL,则它很可能是静态链接的。
如果该库来自第三方,我会假设它使用不同版本的 CRT。
当然,这并不是 100% 准确,因为您正在查看的库可能与另一个与 CRT 链接的库链接。
As far as I know, there isn't a (safe) way to check which version of the CRT a library is statically linked with.
You can, however, check the binaries with a program such as DependencyWalker. If you don't see any DLLs beginning with MSVC in the dependency list, it's most likely statically linked.
If the library is from a third part, I'd just assume it's using a different version of the CRT.
This isn't 100% accurate, of course, because the library you're looking at could be linked with another library that links with the CRT.
这篇 MSDN 文章(此处)指出,您应该通过链接器错误 LNK4098 来解决该问题。它还表明跨 CRT 边界传递任何 CRT 句柄可能会导致问题,并提到区域设置、低级文件 I/O 和内存分配作为示例。
This MSDN article (here) says that you should be alterted to the problem by linker error LNK4098. It also suggests that passing any CRT handles across a CRT boundary is likely to cause trouble, and mentions locale, low-level file I/O and memory allocation as examples.