静态初始化时发生访问冲突

18

我正在使用Visual Studio 2015在Windows 7上开发一个应用程序。该应用程序具有C#前端,C ++ CLR封装器和C ++本机代码。

当使用C ++本机代码在函数作用域内初始化静态变量时,我的应用程序会在Windows Server 2003 Enterprise SP2上崩溃,而在Windows 7或Windows Server 2012上则不会。虽然我知道Windows Server 2003已停止支持,但是我必须为该平台提供支持几个月,并且Visual Studio 2015提供了平台工具集以支持它。

我创建了一个小的可重现示例,您可以在最后找到它。

仅当涉及所有三个部分(C#,C ++ CLR,C ++)时才会发生崩溃。如果我删除任何一个,崩溃就会消失。

仅当定义了自定义构造函数时才会发生崩溃。如果我删除构造函数,则崩溃会消失。

我不是汇编专家,但对我来说,似乎崩溃是由检查是否需要静态初始化的代码引起的。甚至没有调用构造函数。

我的问题是:为什么它会在Windows Server 2003上崩溃?我是否遗漏了一些重要的项目设置?

错误信息

Unhandled exception at 0x1000167E (Native.dll) in Managed.exe.dmp: 0xC0000005: Access violation reading location 0x00000000.

Visual C#控制台应用程序"Managed.exe"

Program.cs

// Target framework: .NET Framework 4
// Platform target: x86

using System;

namespace Managed
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.Write("Press enter to start test...");
            Console.ReadLine();

            Native.Wrapper wrapper = new Native.Wrapper();
            Console.WriteLine("Test was successful");

            Console.Write("Press enter to exit...");
            Console.ReadLine();
        }
    }
}

Visual C++ CLR 类库 "Native.dll"

Wrapper.hpp

#pragma once

namespace Native
{

public ref class Wrapper
{
public:
    Wrapper();

}; // public ref class Wrapper

} // namespace Native

Wrapper.cpp

// Platform Toolset: Visual Studio 2015 - Windows XP (v140_xp)
// Common Language Runtime Support: Common Language Runtime Support (/clr)
// .NET Target Framework Version: v4.0
// Warning Level: Level4
// Treat Warnings As Errors: Yes (/WX)
// Precompiled Header: Not Using Precompiled Headers
// SubSystem: Console (/SUBSYSTEM:CONSOLE)
// Optimization: Disabled (/Od)

#pragma once

#include "Wrapper.hpp"
#include "Caller.hpp"

namespace Native
{

Wrapper::Wrapper()
{
    Caller* caller = new Caller();
    delete caller;
}

} // namespace Native

Caller.hpp

#pragma once

namespace Native
{

class Caller
{
public:
    Caller();

}; // class Caller

} // namespace Native

Caller.cpp

// Platform Toolset: Visual Studio 2015 - Windows XP (v140_xp)
// Common Language Runtime Support: No Common Language RunTime Support
// Warning Level: Level4
// Treat Warnings As Errors: Yes (/WX)
// Precompiled Header: Not Using Precompiled Headers
// SubSystem: Console (/SUBSYSTEM:CONSOLE)
// Optimization: Disabled (/Od)

#include "Caller.hpp"
#include "Singleton.hpp"

namespace Native
{

Caller::Caller()
{
    Singleton::GetInstance()->DoSomething();
}

} // namespace Native

单例模式.hpp

#pragma once

#include <iostream>

namespace Native
{

class Singleton
{
public:
    Singleton() // !!! remove constructor to prevent crash !!!
    { }

    static Singleton* GetInstance()
    {
        static Singleton Instance; // !!! crashes here !!!
        return &Instance;
    }

    void DoSomething()
    {
        std::wcout << L"Doing something...\n";
    }

}; // class Singleton

} // namespace Native

反汇编

    static Singleton* GetInstance()
    {
10001650  push        ebp  
10001651  mov         ebp,esp  
10001653  push        0FFFFFFFFh  
10001655  push        10006A8Ch  
1000165A  mov         eax,dword ptr fs:[00000000h]  
10001660  push        eax  
10001661  mov         eax,dword ptr ds:[1001B334h]  
10001666  xor         eax,ebp  
10001668  push        eax  
10001669  lea         eax,[ebp-0Ch]  
1000166C  mov         dword ptr fs:[00000000h],eax  
        static Singleton Instance;
10001672  mov         eax,dword ptr ds:[1001B5D0h]  
10001677  mov         ecx,dword ptr fs:[2Ch]  
1000167E  mov         edx,dword ptr [ecx+eax*4] // !!! access violation here !!!
10001681  mov         eax,dword ptr ds:[1001B5A4h]  
10001686  cmp         eax,dword ptr [edx+4]  
1000168C  jle         Native::Singleton::GetInstance+79h (100016C9h)  

寄存器

EAX = 00000000 EBX = 00000000 ECX = 00000000 EDX = 006A0003 ESI = 001647C8
EDI = 0012F3BC EIP = 1000167E ESP = 0012F394 EBP = 0012F3A4 EFL = 00010282 

编辑1

在本地调试时,崩溃不会发生,但汇编中会显示更多的符号:

    static Singleton* GetInstance()
    {
0FBD1650  push        ebp  
0FBD1651  mov         ebp,esp  
0FBD1653  push        0FFFFFFFFh  
0FBD1655  push        offset __ehhandler$?GetInstance@Singleton@Native@@SAPAV12@XZ (0FBD86BCh)  
0FBD165A  mov         eax,dword ptr fs:[00000000h]  
0FBD1660  push        eax  
0FBD1661  mov         eax,dword ptr [___security_cookie (0FBF03CCh)]  
0FBD1666  xor         eax,ebp  
0FBD1668  push        eax  
0FBD1669  lea         eax,[ebp-0Ch]  
0FBD166C  mov         dword ptr fs:[00000000h],eax  
        static Singleton Instance;
0FBD1672  mov         eax,dword ptr [__tls_index (0FBF0668h)]  
0FBD1677  mov         ecx,dword ptr fs:[2Ch]  
0FBD167E  mov         edx,dword ptr [ecx+eax*4]  
0FBD1681  mov         eax,dword ptr [TSS0<`template-parameter-2',Native::Singleton::tInstance,Native::Singleton * * const volatile,void,int, ?? &> (0FBF063Ch)]  
0FBD1686  cmp         eax,dword ptr [edx+4]  
0FBD168C  jle         Native::Singleton::GetInstance+79h (0FBD16C9h)  
__tls_index符号似乎属于某个线程局部存储(从名称猜测)。这与Magic statics相匹配,后者在参考实现中使用线程局部存储作为性能优化。当发生崩溃时,线程局部存储会返回0
可能是Windows Server 2003上运行时环境管理和初始化线程局部存储的错误吗?

Edit 2

通过Microsoft Connect报告为错误:Bug report
2个回答

31

以下是Microsoft作为对我在Microsoft Connect上的错误报告发布的答案:

Windows Server 2003和Windows XP存在动态加载DLL(通过LoadLibrary)时遇到问题,因为这会使用线程局部存储,而线程安全静态内部使用线程局部存储来提供高效执行,当已经初始化了静态局部变量时。由于这些系统已不受支持,因此极不可能为这些系统创建补丁以添加这种支持,就像Vista和更新的操作系统中一样,我们不愿惩罚支持OSes的性能,以向旧版本不再受支持的系统提供此功能。

要解决此问题,您可以使用/Zc:threadSafeInit-来禁用线程安全初始化代码,并避免线程局部变量。当然,这样做会使初始化代码回退到VS2013模式,并且不是线程安全的,因此只有在不依赖于本地静态变量的线程安全性时才适用此选项。


谢谢,这对我很有帮助。在我的情况下,我正在将一个VS 2013托管C++项目升级到Windows 7上的VS 2015,但托管代码在到达我的“main”方法之前就会崩溃。调试器甚至没有显示任何东西,直到我将其切换为仅调试本机代码。那表明崩溃与“thread_safe_statics”中的初始化代码有关。这就把我带到了这里。一旦我切换到使用/Zc:threadSafeInit-选项,我的问题就消失了。当然,这可能意味着我的某些静态数据有点奇怪,但现在我只是很高兴我的项目正在运行。谢谢! - AZDean
1
我也遇到了使用VS2017构建的二进制文件的问题。/Zc:threadSafeInit-确实解决了在WinXP上构造静态局部对象时崩溃的问题,但是使用此选项似乎根本没有调用对象的构造函数。至少,虚表没有设置。因此,对我来说唯一的解决方案是将静态局部对象替换为静态全局对象。 - LubosD
谢谢,它解决了我的问题。我正在使用VS2017编译C++ dll。该dll在一个函数内有一个静态对象。在Windows 10上一切正常,但在Windows XP上加载库并第一次调用静态对象的构造函数时会崩溃。该dll是单线程的,错误信息如下:“无法加载DLL。内存位置访问无效”。 - Tides
请注意,如果您的 C++ 代码使用 /Zc:threadSafeInit- 编译(在 VS 2017 15.9 中引入,请参见此处),预处理器 __cpp_threadsafe_static_init不会被 #define。因此,您可以为两种情况编写代码。 - Phi

-1

你的构造函数并没有被删除,因为它是公共的。 将其移动到类声明的私有部分。


访问规范与此错误无关。使用受保护或私有构造函数仍会发生相同的崩溃。我将构造函数设置为公共以简化此示例。这并不意味着它是单例最佳实践的演示。 - Backbone
如果您将类作用域中的静态变量移动,会发生什么? - Sebacote
@SébastienCôté 当移动到类作用域时,崩溃问题已经解决。 - Backbone
"magic statics"是 C++/11 的一个新特性,首次于VS2015中支持。如果可能的话,您可能应该暂时坚持使用类作用域静态变量... - Sebacote

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