考虑:
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
也会重现。)
答案 0 :(得分: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 helper函数返回0x0000000FF,等于State.BUG,需要在比较值之前将其转换为-1(0xFFFFFFFF)。 MOVSX指令是Sign eXtension指令。
同样的事情在013F04CC再次发生,但这次有没有 MOVSX指令进行相同的转换。这就是芯片跌落的地方,CMP指令将0xFFFFFFFF与0x000000FF进行比较,这是假的。所以这是一个遗漏错误,代码生成器无法再次发出MOVSX来执行相同的sbyte到int转换。
这个错误特别不寻常的是,当你启用优化器时它可以正常工作,它现在知道在两种情况下都使用MOVSX。
这个bug长期未被发现的可能原因是使用sbyte作为枚举的基本类型。非常罕见。使用多维数组也是有用的,这种组合是致命的。
否则我会说一个非常严重的错误。可能有多普遍,很难猜测,我只有4.6.1 x86抖动进行测试。 x64和3.5 x86抖动生成非常不同的代码并避免此错误。继续使用的临时解决方法是删除作为枚举基类型的sbyte,并将其作为默认值 int ,因此不需要使用符号扩展名。
您可以在connect.microsoft.com上提交错误,链接到此Q + A应该足以告诉他们需要知道的一切。如果你不想花时间让我知道,我会照顾它。
答案 1 :(得分: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
BUG
255
由于我忽略(截至目前) s[0, 0]
的原因在评估之前被强制转换为一个字节,这就是它声称a == s[0,0]
为假的原因。