为什么多维数组的枚举值不等于自身?


151

考虑:

using System;

public class Test
{
    enum State : sbyte { OK = 0, BUG = -1 }

    static void Main(string[] args)
    {
        var s = new State[1, 1];
        s[0, 0] = State.BUG;
        State a = s[0, 0];
        Console.WriteLine(a == s[0, 0]); // False
    }
}

如何解释呢?在x86 JIT中运行时,它会在Visual Studio 2015的调试版本中发生。在x64 JIT中运行或运行的发行版将按预期输出True。

要从命令行复制:

csc Test.cs /platform:x86 /debug

/debug:pdbonly/debug:portable并且/debug:full还复制。)


2
ideone.com/li3EzY是真的。添加有关.net版本,IDE,编译器的更多信息
Backs

1
同样在这里。但是在摆弄了项目设置之后,我发现取消选中“构建”选项卡中的“首选32位”复选框会使其按预期方式工作-返回true。因此,对我来说,这似乎是一个WoW64问题。
Dmitry Rotay '16

2
看来您指出了框架中的错误。
Fabien PERRONNET '16

1
有趣的是,运行破损的代码ildasm,然后对其进行ilasm“修复” ...
Jon Skeet

2
/debug=IMPL旗叶它打破; /debug=OPT“修复”它。
乔恩·斯基特

Answers:


163

您在.NET 4 x86抖动中发现了代码生成错误。这是非常不寻常的,只有在代码未优化时它才会失败。机器代码如下:

        State a = s[0, 0];
013F04A9  push        0                            ; index 2 = 0
013F04AB  mov         ecx,dword ptr [ebp-40h]      ; s[] reference
013F04AE  xor         edx,edx                      ; index 1 = 0
013F04B0  call        013F0058                     ; eax = s[0, 0]
013F04B5  mov         dword ptr [ebp-4Ch],eax      ; $temp1 = eax 
013F04B8  movsx       eax,byte ptr [ebp-4Ch]       ; convert sbyte to int
013F04BC  mov         dword ptr [ebp-44h],eax      ; a = s[0, 0]
        Console.WriteLine(a == s[0, 0]); // False
013F04BF  mov         eax,dword ptr [ebp-44h]      ; a
013F04C2  mov         dword ptr [ebp-50h],eax      ; $temp2 = a
013F04C5  push        0                            ; index 2 = 0
013F04C7  mov         ecx,dword ptr [ebp-40h]      ; s[] reference 
013F04CA  xor         edx,edx                      ; index 1 = 0
013F04CC  call        013F0058                     ; eax = s[0, 0]
013F04D1  mov         dword ptr [ebp-54h],eax      ; $temp3 = eax 
                                               ; <=== Bug here!
013F04D4  mov         eax,dword ptr [ebp-50h]      ; a == s[0, 0] 
013F04D7  cmp         eax,dword ptr [ebp-54h]  
013F04DA  sete        cl  
013F04DD  movzx       ecx,cl  
013F04E0  call        731C28F4  

带有很多临时性和代码重复的令人毛骨悚然的事情,这对于未优化的代码是正常的。值得注意的是013F04B8处的指令,即发生了从sbyte到32位整数的必要转换。数组getter辅助函数返回0x0000000FF,等于State.BUG,在比较该值之前需要将其转换为-1(0xFFFFFFFF)。MOVSX指令是符号扩展指令。

013F04CC再次发生相同的情况,但是这次没有 MOVSX指令进行相同的转换。那就是芯片掉落的地方,CMP指令将0xFFFFFFFF与0x000000FF进行比较,这是错误的。因此,这是一个遗漏错误,代码生成器无法再次发出MOVSX来执行相同的字节数到int的转换。

此错误特别不寻常的是,当启用优化器时,此错误可以正常运行,现在知道在两种情况下都使用MOVSX。

长时间未发现此错误的可能原因是使用sbyte作为枚举的基本类型。很少做。使用多维数组也是有帮助的,这种组合是致命的。

否则,我会说一个非常严重的错误。很难猜到它有多广泛,我只测试了4.6.1 x86抖动。x64和3.5 x86抖动会生成完全不同的代码,并避免出现此错误。继续进行的临时解决方法是删除sbyte作为枚举基类型,并将其作为默认值int,因此不需要符号扩展。

您可以在connect.microsoft.com上提交该错误,链接到该Q + A应该足以告诉他们他们需要知道的一切。如果您不想花时间,请告诉我,我会解决。


33
好的,可靠的数据以及导致这种奇怪问题的确切原因,总是很高兴阅读+1。
Lasse V. Karlsen

11
请发布指向connect.microsoft.com文章的链接,以便我们对其投票。
汉斯·帕桑

我认为使用byte代替sbyte也是可以的,如果真实代码与ORM一起使用时可能更可取,因为您不希望数据库中的标志占用额外的空间。
Voo

6
我将错误发布在dotnet / coreclr而不是connect上,您将直接进入JIT开发人员。
Lucas Trzesniewski '16

8
我是Microsoft JIT团队的一名开发人员。我已经重现了该错误,并在内部为其打开了一个问题(在github上尚未公开发售x86 JIT)。关于何时修复此问题,我预计我们将在工具的下一个主要版本中包含此修复程序。如果此错误对业务有影响,并且您需要较早地进行修复,请提出connect(connect.microsoft.com)问题,以便我们查看影响以及为更快地获得修复所需要的替代方法。
罗素C.哈德利

8

让我们考虑一下OP的声明:

enum State : sbyte { OK = 0, BUG = -1 }

由于该错误仅在BUG负数(从-128到-1)且State是一个有符号字节的枚举时发生,因此我开始假设某个地方存在强制转换问题。

如果运行此命令:

Console.WriteLine((sbyte)s[0, 0]);
Console.WriteLine((sbyte)State.BUG);
Console.WriteLine(s[0, 0]);
unchecked
{
    Console.WriteLine((byte) State.BUG);
}

它会输出:

255

-1

臭虫

255

由于我忽略的原因(到目前为止) s[0, 0]在评估之前将其强制转换为一个字节,因此它声称它a == s[0,0]是错误的。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.