web-dev-qa-db-fra.com

Comment vérifier le nombre d'octets consommés par une structure?

Si je crée une structure relativement grande, comment puis-je calculer les octets qu'elle occupe en mémoire?

Nous pouvons le faire manuellement, mais si la structure est suffisamment grande, comment le faire? Y a-t-il un morceau de code ou une application?

44
Simsons

Vous pouvez utiliser l'opérateur sizeof ou SizeOf .
Il existe des différences entre ces options, voir les liens de référence pour en savoir plus.

Quoi qu'il en soit, un bon moyen d'utiliser cette fonction est d'avoir une méthode générique ou une méthode d'extension comme celle-ci:

static class Test
{
  static void Main()
  {
    //This will return the memory usage size for type Int32:
    int size = SizeOf<Int32>();

    //This will return the memory usage size of the variable 'size':
    //Both lines are basically equal, the first one makes use of ex. methods
    size = size.GetSize();
    size = GetSize(size);
  }

  public static int SizeOf<T>()
  {
    return System.Runtime.InteropServices.Marshal.SizeOf(typeof(T));
  }

  public static int GetSize(this object obj)
  {
    return System.Runtime.InteropServices.Marshal.SizeOf(obj);
  }
}
31
Shimmy

Les structures sont depuis très longtemps des bêtes gênantes en génie informatique. Leur disposition de mémoire est très dépendante du matériel. Pour les rendre efficaces, leurs membres doivent être alignés afin que le CPU puisse lire et écrire leurs valeurs rapidement sans avoir à multiplexer les octets pour s'adapter à la largeur du bus mémoire. Chaque compilateur a sa propre stratégie de compression des membres, souvent dirigée, par exemple, par la directive #pragma pack dans un programme C ou C++.

Ce qui est correct, mais plutôt un problème dans les scénarios d'interopérabilité. Où un morceau de code peut faire des hypothèses différentes sur la disposition de la structure qu'un autre morceau, compilé par un compilateur différent. Vous pouvez le voir dans COM, la solution grand-père de .NET pour la programmation d'interopérabilité. COM a très un support médiocre pour la gestion des structures. Il ne les prend pas en charge en tant que type d'automatisation natif, mais dispose d'une solution de contournement via l'interface IRecordInfo. Ce qui permet à un programme de découvrir la disposition de la mémoire au moment de l'exécution grâce à une déclaration explicite de la structure dans une bibliothèque de types. Ce qui fonctionne bien, mais est assez inefficace.

Les concepteurs .NET ont pris une décision très courageuse et correcte pour résoudre ce problème. Ils ont rendu la disposition de la mémoire d'une structure complètement impossible à découvrir. Il n'existe aucun moyen documenté de récupérer le décalage d'un membre. Et par extension, aucun moyen de découvrir la taille de la structure. La réponse préférée de tous, utiliser Marshal.SizeOf () n'est en fait pas la solution. Cela renvoie la taille de struct ne fois qu'il est marshalé, la taille que vous devez passer, par exemple, à Marshal.AllocCoTaskMem () avant d'appeler Marshal.StructureToPtr. Cela arrange et aligne les membres de la structure en fonction de l'attribut [StructLayout] associé à la structure. Notez que cet attribut n'est pas requis pour les structures (comme c'est le cas pour les classes), le runtime implémente un attribut par défaut qui utilise l'ordre déclaré pour les membres.

Un effet secondaire très agréable de la mise en page qui ne peut pas être découverte est que le CLR peut jouer des tours avec lui. Lors du compactage des membres de la structure et de leur alignement, la mise en page peut obtenir des trous qui ne stockent aucune donnée. Octets de remplissage appelés. Étant donné que la mise en page est impossible à découvrir, le CLR peut en fait utiliser le remplissage. Il déplace un membre s'il est suffisamment petit pour s'adapter à un tel trou. Vous obtiendrez maintenant en fait une structure dont la taille est plus petite que ce qui serait normalement requis étant donné la disposition de la structure déclarée. Et, notamment, Marshal.SizeOf () retournera la valeur faux pour la taille de la structure, il retournera une valeur trop grande.

En bref, il n'y a pas de moyen général d'obtenir une valeur précise pour la taille de la structure par programme. La meilleure chose à faire est de ne pas poser la question. Marshal.SizeOf () vous donnera une estimation, en supposant que la structure est blittable. Si vous avez besoin d'une valeur précise pour une raison quelconque, vous pouvez consulter le code machine généré d'une méthode qui déclare une variable locale du type de structure et le comparer à la même méthode sans cette variable locale. Vous verrez la différence dans l'ajustement du pointeur de pile, l'instruction "sub esp, xxx" en haut de la méthode. Bien sûr, cela dépendra de l'architecture, vous obtiendrez généralement une structure plus grande en mode 64 bits.

108
Hans Passant

Vous pouvez soit utiliser le mot clé sizeof() pour les structures définies par l'utilisateur qui ne contiennent pas de champs ou de propriétés qui sont des types de référence, ou utiliser le Marshal.SizeOf(Type) ou Marshal.SizeOf(object) pour obtenir la taille non gérée d'un type ou d'une structure qui a un séquentiel ou explicite layout .

9
Allon Guralnek

J'ai écrit une toute petite bibliothèque en CIL ( . NET 's Assembly language) pour exposer des fonctionnalités intéressantes qui ne sont pas disponibles en C #. J'ai éclaté l'instruction sizeof.

Il diffère considérablement de l'opérateur sizeof en C #. Fondamentalement, il obtient la taille d'une structure (ou d'un type de référence, qui agit de manière amusante avec quelques optimisations), y compris le remplissage et tout. Donc, si vous deviez créer un tableau de T, vous pouvez utiliser sizeof pour déterminer la distance entre chaque élément du tableau en octets . C'est également un code entièrement vérifiable et géré. Notez que dans Mono il y avait un bogue (pré-3.0?) Qui provoquait un rapport incorrect de la taille des types de référence, ce qui s'étendrait aux structures contenant des types de référence.

Quoi qu'il en soit, vous pouvez télécharger la bibliothèque sous licence BSD (et CIL) à partir de BitBucket . Vous pouvez également voir un exemple de code et quelques détails supplémentaires sur mon blog .

7
Earlz

0. Pour l'exemple de code:

using System;
using System.Diagnostics;
using System.Reflection.Emit;
using System.Runtime.InteropServices;

1. Structure de démonstration

[Serializable, StructLayout(LayoutKind.Sequential, Pack = 128)]
public struct T
{
    public int a;
    public byte b;
    public int c;
    public String d;
    public short e;
};

2. Soustraction de pointeurs gérés:

/// Return byte-offset between managed addresses of struct instances 'hi' and 'lo'
public static long IL<T1,T2>.RefOffs(ref T1 hi, ref T2 lo) { ... }
public static class IL<T1, T2>
{
    public delegate long _ref_offs(ref T1 hi, ref T2 lo);

    public static readonly _ref_offs RefOffs;

    static IL()
    {
        var dm = new DynamicMethod(
            Guid.NewGuid().ToString(),
            typeof(long),
            new[] { typeof(T1).MakeByRefType(), typeof(T2).MakeByRefType() },
            typeof(Object),
            true);

        var il = dm.GetILGenerator();
        il.Emit(OpCodes.Ldarg_0);
        il.Emit(OpCodes.Ldarg_1);
        il.Emit(OpCodes.Sub);
        il.Emit(OpCodes.Conv_I8);
        il.Emit(OpCodes.Ret);
        RefOffs = (_ref_offs)dm.CreateDelegate(typeof(_ref_offs));
    }
};

3. Révéler la disposition des structures internes gérées:

static class demonstration
{
    /// Helper thunk that enables automatic type-inference from argument types
    static long RefOffs<T1,T2>(ref T1 hi, ref T2 lo) => IL<T1,T2>.RefOffs(ref hi, ref lo);

    public static void Test()
    {
        var t = default(T);
        var rgt = new T[2];

        Debug.Print("Marshal.Sizeof<T>: {0,2}", Marshal.SizeOf<T>());
        Debug.Print("&rgt[1] - &rgt[0]: {0,2}", RefOffs(ref rgt[1], ref rgt[0]));

        Debug.Print("int      &t.a      {0,2}", RefOffs(ref t.a, ref t));
        Debug.Print("byte     &t.b      {0,2}", RefOffs(ref t.b, ref t));
        Debug.Print("int      &t.c      {0,2}", RefOffs(ref t.c, ref t));
        Debug.Print("String   &t.d      {0,2}", RefOffs(ref t.d, ref t));
        Debug.Print("short    &t.e      {0,2}", RefOffs(ref t.e, ref t));
    }
};

4. Résultats et discussion

Le paramètre StructLayout(..., Pack) peut être ajouté à la déclaration de struct T Avec l'une des valeurs suivantes: {0, 1, 2, 4, 8, 16 , 32, 64, 128} . La valeur par défaut lorsque Pack n'est pas spécifiée - ou de manière équivalente avec Pack=0 - définit l'emballage égal à IntPtr.Size (4 Sur x86, 8 sur x64).

Les résultats de l'exécution du programme ci-dessus montrent que la valeur Pack n'affecte que la taille de marshaling signalée par Marshal.SizeOf, Et non la taille réelle d'un seul T image mémoire, supposée être le décalage d'octet entre les instances physiquement adjacentes. Le code de test mesure cela via le tableau géré par diagnostic new T[2] Affecté à rgt.

========= x86 =================== x64 ==========

-------- Pack=1 ---------------- Pack=1 --------
Marshal.Sizeof(): 15Marshal.Sizeof(): 19
&rgt[1] - &rgt[0]: 16&rgt[1] - &rgt[0]: 24

-------- Pack=2 ---------------- Pack=2 --------
Marshal.Sizeof(): 16Marshal.Sizeof(): 20
&rgt[1] - &rgt[0]: 16&rgt[1] - &rgt[0]: 24

--- Pack=4/0/default ----------- Pack=4 --------
Marshal.Sizeof(): 20Marshal.Sizeof(): 24
&rgt[1] - &rgt[0]: 16&rgt[1] - &rgt[0]: 24

-------- Pack=8 ----------- Pack=8/0/default ---
Marshal.Sizeof(): 20Marshal.Sizeof(): 32
&rgt[1] - &rgt[0]: 16&rgt[1] - &rgt[0]: 24

-- Pack=16/32/64/128 ----- Pack=16/32/64/128 ---
Marshal.Sizeof(): 20Marshal.Sizeof(): 32
&rgt[1] - &rgt[0]: 16&rgt[1] - &rgt[0]: 24

Comme indiqué, nous trouvons que par architecture ( x86 , x64 ) , le disposition des champs gérés est cohérent quel que soit le paramètre Pack. Voici les décalages de champ gérés réels, à nouveau pour les modes 32 et 64 bits, comme indiqué par le code ci-dessus:

┌─offs─┐
field type size x86 x64
===== ====== ==== === ===
a int 4 4 8
b byte 1 14 18
c int 4 8 12
d String 4/8 0 0
e short 2 12 16

La chose la plus importante à noter dans ce tableau est que, (comme mentionné par Hans ), les décalages de champ rapportés ne sont pas monotones par rapport à leur ordre de déclaration. Les champs des instances ValueType sont toujours réorganisés afin que tous les champs de type référence soient placés en premier. Nous pouvons voir que le champ String d est à l'offset 0.

Une réorganisation supplémentaire optimise l'ordre des champs afin de partager l'excès de remplissage interne qui serait autrement gaspillé. Nous pouvons le voir avec byte field b, qui a été déplacé du deuxième champ déclaré, jusqu'au dernier.

Naturellement, en triant les lignes du tableau précédent, nous pouvons révéler la véritable disposition interne gérée d'un . NET ValueType. Notez que nous pouvons obtenir cette mise en page malgré l'exemple de struct T contenant une référence gérée (String d) Et donc non blittable :

============= x86 ========================= x64 ============
field type size offs endfield type size offs end
===== ====== ==== ==== ======== ====== ==== ==== ===
d String 4 0 … 4d String 8 0 … 8
a int 4 4 … 8a int 4 8 … 12
c int 4 8 … 12c int 4 12 … 16
e short 2 12 … 14e short 2 16 … 18
b byte 1 14 … 15b byte 1 18 … 19

internal padding: 1 15 … 16internal padding: 5 19 … 24

x86 managed total size: 16x64 managed total size: 24

Auparavant, nous avons déterminé la taille d'une seule instance de structure gérée en calculant la différence de décalage d'octet entre les instances adjacentes. En tenant compte de cela, les dernières lignes du tableau précédent montrent de manière triviale le remplissage auquel le [~ # ~] clr [~ # ~] s'applique en interne la fin de l'exemple struct T. Rappelez-vous bien sûr que ce rembourrage interne est fixé par le CLR et entièrement hors de notre contrôle.

5. Coda
Pour être complet, ce dernier tableau indique la quantité de remplissage qui sera synthétisée à la volée pendant marshaling. Notez que dans certains cas, ce remplissage Marshal est négatif par rapport à la taille gérée interne. Par exemple, même si la taille gérée interne d'un T dans x64 est de 24 octets, la structure émise par le marshaling peut être de 19 ou 20 octets avec Pack=1 Ou Pack=2, Respectivement.

pack size offs endpack size offs end
============= ==== ==== ================ ==== ==== ===
1 0 15 … 151 0 19 … 19
2 1 15 … 162 1 19 … 20
4/8/16/32/64… 5 15 … 204/8/16/32/64… 5 19 … 24

5
Glenn Slayden

Dans . NET Core , l'instruction sizeof CIL a été exposée via la classe Unsafe récemment ajoutée. Ajouter une référence au System.Runtime.CompilerServices.Unsafe package et faites ceci:

int size = Unsafe.SizeOf<MyStruct>();

Cela fonctionne également pour les types de référence (retournera 4 ou 8 selon l'architecture de votre ordinateur).

4
James Ko

Vous voulez utiliser System.Runtime.InteropServices.Marshal.SizeOf () :

struct s
{
    public Int64 i;
}

public static void Main()
{
    s s1;
    s1.i = 10;          
    var s = System.Runtime.InteropServices.Marshal.SizeOf(s1);
}
3
anishMarokey

Vous pouvez également utiliser System.Runtime.InteropServices.Marshal.SizeOf() pour obtenir la taille en octets.

2
Jeff Mercado