2009-12-16 4 views
11

je les types de valeurs .NET suivantes:Mise en page de type de valeur .NET en mémoire

[StructLayout(LayoutKind.Sequential)] 
public struct Date 
{ 
    public UInt16 V; 
} 

[StructLayout(LayoutKind.Sequential)] 
public struct StringPair 
{ 
    public String A; 
    public String B; 
    public String C; 
    public Date D; 
    public double V; 
} 

J'ai le code qui passe un pointeur vers un type de valeur au code non managé, ainsi que des décalages découverts en appelant système .Runtime.InteropServices.Marshal.OffsetOf. Le code non géré remplit la date et les valeurs doubles.

Les décalages qui sont rapportés pour la struct StringPair sont exactement ce que j'attendais: 0, 8, 16, 24, 32

je le code suivant dans une fonction de test:

FieldInfo[] fields = typeof(StringPair).GetFields(BindingFlags.Instance|BindingFlags.Public); 

for (int i = 0; i < fields.Length; i++) 
{ 
    int offset = System.Runtime.InteropServices.Marshal.OffsetOf(typeof(StringPair), fields[i].Name).ToInt32(); 

    Console.WriteLine(String.Format(" >> field {0} @ offset {1}", fields[i].Name, offset)); 
} 

Qui imprime exactement ces décalages.

>> field A @ offset 0 
>> field B @ offset 8 
>> field C @ offset 16 
>> field D @ offset 24 
>> field V @ offset 32 

Je puis un code de test: foreach (paire StringPair par paires) {Date de d = pair.D; double v = pair.V; ...

qui a l'assembleur suivant qui lui est associé dans le débogueur:

   Date d = pair.D; 
0000035d lea   rax,[rbp+20h] 
00000361 add   rax,20h 
00000367 mov   ax,word ptr [rax] 
0000036a mov   word ptr [rbp+000000A8h],ax 
00000371 movzx  eax,word ptr [rbp+000000A8h] 
00000378 mov   word ptr [rbp+48h],ax 

       double v = pair.V; 
0000037c movsd  xmm0,mmword ptr [rbp+38h] 
00000381 movsd  mmword ptr [rbp+50h],xmm0 

On charge le champ D à décalage 32 (0x20) et le champ V à décalage 24 (0x38-0x20). Le JIT a changé l'ordre des choses. Le débogueur Visual Studio affiche également cet ordre inversé.

Pourquoi? Je me suis arraché les cheveux pour essayer de voir où ma logique tourne mal. Si j'échange l'ordre de D et V dans la structure alors tout fonctionne, mais ce code doit être capable de gérer une architecture de plugin où d'autres développeurs ont défini la structure, et on ne peut pas s'attendre à se souvenir de règles de disposition mystérieuses.

Répondre

11

Les informations que vous obtenez de la classe Marshal est pertinent que si le type se fait marshalée. La disposition de la mémoire interne d'une structure gérée ne peut être détectée par aucun moyen documenté, si ce n'est jeter un coup d'œil au code d'assemblage.

Ce qui signifie que le CLR est libre de réorganiser la structure de la structure et d'optimiser l'emballage. L'échange des champs D et V rend votre structure plus petite en raison des exigences d'alignement d'un double. Il enregistre 6 octets sur votre machine 64 bits.

Je ne sais pas pourquoi ce serait un problème pour vous, il ne devrait pas être. Envisager Marshal.StructureToPtr() pour obtenir la structure disposée comme vous le souhaitez.

+1

Merci - J'avais manqué le fait que les méthodes Marshall. * S'appliquaient uniquement au pointeur marshalé. Pour des raisons de performances, j'espérais éviter une copie supplémentaire des données, mais cela semble plutôt inévitable si je veux supporter des structures arbitraires –

+1

+1 pour clarifier que la structure JIT est une question complètement distincte de ce que le CLR spécifie, puisque-- en principe - il ne peut pas être vu par - et ne peut donc pas importer aux - programmes gérés. J'ai remarqué que le JITer semble placer des champs gérés avant non gérés. –

+0

La disposition exacte des bits dans la mémoire de (une instance de) un type de valeur peut avoir une certaine pertinence pour décider d'implémenter IEquatable (et les remplacements recommandés de object.Equals et GetHashCode). Si vous n'implémentez pas IEquatable, je crois que le code généré par défaut effectue une comparaison bit à bit sur les bits de la mémoire. S'il y a des GAPS dans la mise en page, vous risquez de perdre du temps à comparer des bits sans importance (en C++, vous risquez aussi de prendre des risques puisque ces bits peuvent ne pas être initialisés - mais je pense qu'ils sont toujours en C#). Connaître la mise en page informe la décision sur ce qu'il faut implémenter. –

13

Si vous avez besoin ... mise en page explicite utilisation mise en page explicite ...

[StructLayout(LayoutKind.Explicit)] 
public struct StringPair 
{ 
    [FieldOffset(0)] public String A; 
    [FieldOffset(8)] public String B; 
    [FieldOffset(16)] public String C; 
    [FieldOffset(24)] public Date D; 
    [FieldOffset(32)] public double V; 
} 
+2

J'aurais juré que je l'avais essayé à un moment donné au cours des derniers jours .. mais il ne semble pas fonctionner à présent. Cependant, le point est que le séquentiel * devrait * fonctionner et que le cadre signale les décalages qui ne correspondent pas à ceux qu'il utilise réellement. Je ne veux vraiment pas que les utilisateurs de cette architecture de plugin doivent spécifier un attribut et faire les calculs eux-mêmes pour que cela fonctionne. –

1

Deux choses:

  • StructLayout(Sequential) ne garantit pas l'emballage. Vous pouvez utiliser Pack=1, sinon les plates-formes 32 et 64 bits peuvent être différentes.

  • et chaîne est une référence, pas un pointeur.Si la longueur de chaîne est toujours fixe, vous pouvez utiliser des tableaux char fixes:

    public struct MyArray // This code must appear in an unsafe block 
    { 
        public fixed char pathName[128]; 
    }