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

考虑一下:

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
}
}

这怎么解释呢?它在 VisualStudio2015的调试版本中运行于 x86JIT 中时发生。在 x64 JIT 中运行的版本构建将按预期输出 True。

从命令行复制:

csc Test.cs /platform:x86 /debug

(/debug:pdbonly/debug:portable/debug:full也会复制。)

3019 次浏览

让我们考虑一下观察所的声明:

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

因为只有当 BUG为负(从 -128到 -1)并且 State 是 签名字节的枚举时,才会出现 bug,所以我开始假设在某个地方存在强制转换问题。

如果你运行这个:

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]为 false 的原因。

中发现了代码生成错误。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 helper 函数返回0x0000000FF,等于 State.BUG,在比较值之前需要将其转换为 -1(0xFFFFFFFF)。MOVSX 指令是一个符号扩展指令。

同样的事情再次发生在013F04CC,但是这一次有 没有 MOVSX 指令进行相同的转换。这就是芯片崩溃的地方,CMP 指令将0xFFFFFFFF 与0x000000FF 进行比较,结果为 false。所以这是一个遗漏的错误,代码生成器未能发出 MOVSX 再次执行相同的字节到整数的转换。

这个 bug 特别不寻常的地方在于,当您启用优化器时,它能正常工作,现在它知道在这两种情况下都要使用 MOVSX。

这个 bug 长时间未被检测到的可能原因是使用 sbyte 作为枚举的基类型。非常罕见。使用多维数组也是有益的,这种组合是致命的。

要不然就是个很严重的漏洞。很难猜测它的传播范围有多广,我只有4.6.1 x86抖动来测试。X64和3.5 x86抖动产生非常不同的代码,避免了这个 bug。要继续下去的临时解决方案是删除 sbyte 作为枚举基类型,并将其设为默认值 Int,因此不需要符号扩展。

你可以在 connect.microsoft.com 上提交 bug,链接到这个问答应该足以告诉他们他们需要知道的一切。如果你不想花时间就告诉我,我会处理的。