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

Posted

技术标签:

【中文标题】为啥多维数组的枚举值不等于自身?【英文标题】:Why is the enumeration value from a multi dimensional array not equal to itself?为什么多维数组的枚举值不等于自身? 【发布时间】:2016-08-03 22:57:49 【问题描述】:

考虑:

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 也重现。)

【问题讨论】:

ideone.com/li3EzY 是真的。添加更多关于 .net 版本、IDE、编译器的信息 这里也一样。但是在摆弄项目设置之后,我发现取消选中“构建”选项卡中的“首选 32 位”复选框使其按预期工作 - 返回 true。所以,对我来说,这看起来像是一个 WoW64 问题。 您似乎指出了框架中的错误。 有趣的是,通过ildasm 运行损坏的代码,然后通过ilasm“修复”它... /debug=IMPL 标志使其损坏; /debug=OPT“修复”它。 【参考方案1】:

让我们考虑一下 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] 为假的原因。

【讨论】:

【参考方案2】:

您在 .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 以执行相同的 sbyte 到 int 的转换。

这个错误特别不寻常的是,当您启用优化器时它可以正常工作,它现在知道在两种情况下都使用 MOVSX。

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

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

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

【讨论】:

良好、可靠的数据,以及导致此类奇怪问题的确切原因,阅读总是很愉快,+1。 请发布 connect.microsoft.com 文章的链接,以便我们为它投票。 我认为使用 byte 而不是 sbyte 也应该没问题,如果将真实代码与不希望数据库中的标志占用的 ORM 一起使用,则可能更可取增加额外空间。 我会发布错误 on dotnet/coreclr 而不是连接,您将直接与 JIT 开发人员联系。 我是 Microsoft JIT 团队的开发人员。我已经复制了这个错误并在内部为它打开了一个问题(在 github 上还没有开放 x86 JIT)。就修复时间而言,我预计我们将在工具的下一个主要版本中包含此修复。如果此错误对业务有影响,并且您需要更早地进行修复,请提交连接 (connect.microsoft.com) 问题,以便我们查看影响以及我们有哪些替代方法可以更快地为您修复。

以上是关于为啥多维数组的枚举值不等于自身?的主要内容,如果未能解决你的问题,请参考以下文章

为啥我们既有交错数组又有多维数组?

为啥 Java 没有真正的多维数组?

为啥扩展元素不适合复制多维数组?

为啥扩展元素不适合复制多维数组?

多维动态数组,为啥不起作用?

为啥使用数组作为索引会改变多维 ndarray 的形状?