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.
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 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. –
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. –