151 votes

Pourquoi la valeur de l'énumération d'un tableau multidimensionnel n'est-elle pas égale à elle-même ?

Pensez-y :

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

Comment expliquer ce phénomène ? Il se produit dans les constructions de débogage dans Visual Studio 2015 lors de l'exécution dans le JIT x86. Un build de version ou une exécution dans le JIT x64 imprime True comme prévu.

Pour reproduire à partir de la ligne de commande :

csc Test.cs /platform:x86 /debug

( /debug:pdbonly , /debug:portable y /debug:full se reproduisent également).

163voto

Hans Passant Points 475940

Vous avez trouvé un bug de génération de code dans le jitter x86 de .NET 4. C'est un bogue très inhabituel, il n'échoue que lorsque le code n'est pas optimisé. Le code machine ressemble à ceci :

        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  

Une affaire laborieuse avec beaucoup de temporaires et de duplication de code, c'est normal pour du code non optimisé. L'instruction à 013F04B8 est remarquable, c'est là que la conversion nécessaire de sbyte à un entier de 32 bits se produit. La fonction d'aide array getter a retourné 0x0000000FF, égal à State.BUG, et cela doit être converti en -1 (0xFFFFFFFF) avant que la valeur puisse être comparée. L'instruction MOVSX est une instruction d'extension de signe.

La même chose se produit à nouveau à 013F04CC, mais cette fois il y a pas de L'instruction MOVSX permet d'effectuer la même conversion. C'est là que le bât blesse, l'instruction CMP compare 0xFFFFFFFF avec 0x000000FF et c'est faux. Il s'agit donc d'une erreur d'omission, le générateur de code a omis d'émettre à nouveau l'instruction MOVSX pour effectuer la même conversion de sbyte en int.

Ce qui est particulièrement inhabituel dans ce bogue est que cela fonctionne correctement lorsque vous activez l'optimiseur, il sait maintenant utiliser MOVSX dans les deux cas.

La raison probable pour laquelle ce bogue n'a pas été détecté pendant si longtemps est l'utilisation de sbyte comme type de base de l'enum. C'est assez rare. L'utilisation d'un tableau multidimensionnel est également déterminante, la combinaison est fatale.

Sinon, je dirais que c'est un bug assez critique. Il est difficile de dire à quel point il est répandu, je n'ai que la version 4.6.1 x86 à tester. Les versions x64 et 3.5 x86 génèrent un code très différent et évitent ce bug. La solution temporaire pour continuer à fonctionner est de supprimer sbyte comme type de base de l'enum et de le laisser être le type par défaut, int Il n'est donc pas nécessaire d'étendre le signe.

Vous pouvez signaler le bogue à l'adresse connect.microsoft.com, en établissant un lien vers cette Q+A qui devrait suffire à leur dire tout ce qu'ils ont besoin de savoir. Faites-moi savoir si vous ne voulez pas prendre le temps et je m'en occuperai.

8voto

Thomas Points 2676

Considérons la déclaration de l'OP :

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

Puisque le bogue ne se produit que lorsque BUG est négatif (de -128 à -1) et State est un enum de octet signé J'ai commencé à penser qu'il y avait un problème de distribution quelque part.

Si vous exécutez ceci :

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

il produira :

255

-1

BUG

255

Pour une raison que j'ignore (à partir de maintenant) s[0, 0] est converti en octet avant l'évaluation et c'est pourquoi il prétend que a == s[0,0] est fausse.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X