c# - titel - Wie überprüfe ich die Anzahl der Bytes, die von einer Struktur benötigt werden?




title tag wordpress (6)

Ich habe eine winzige kleine Bibliothek in CIL ( .NET Assembly Language) geschrieben, um einige nette Funktionen zu entdecken, die in C # nicht verfügbar sind. Ich habe die sizeof Instruktion ausgebrochen.

Es unterscheidet sich erheblich von der sizeof Operators in C #. Grundsätzlich bekommt es die Größe einer Struktur (oder eines Referenztyps, die bei einigen Optimierungen witzig wirkt) inklusive Padding und All. Wenn Sie also ein Array von T erstellen, können Sie mit sizeof den Abstand zwischen jedem Arrayelement in Byte bestimmen. Es ist auch vollständig verifizierbar und verwaltet Code. Beachten Sie, dass es in Mono einen Fehler gab (vor 3.0?), Der dazu führte, dass die Größe der Referenztypen falsch gemeldet wurde, was zu Strukturen mit Referenztypen führen würde.

Wie auch immer, Sie können die BSD lizenzierte Bibliothek (und CIL) von BitBucket herunterladen. Sie können auch einen Beispielcode und einige weitere Details in meinem Blog sehen .

Wenn ich eine relativ große Struktur erstelle, wie kann ich die Bytes berechnen, die sie im Speicher belegt?

Wir können es manuell tun, aber wenn die Struktur groß genug ist, wie machen wir das? Gibt es einen Code-Chunk oder eine Anwendung?


In .NET Core wurde die sizeof CIL-Befehls über die kürzlich hinzugefügte Unsafe Klasse Unsafe . Fügen Sie einen Verweis auf das System.Runtime.CompilerServices.Unsafe Paket hinzu, und führen Sie System.Runtime.CompilerServices.Unsafe :

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

Es funktioniert auch für Referenztypen (gibt 4 oder 8 zurück, abhängig von der Architektur Ihres Computers).


Sie können entweder das Schlüsselwort sizeof sizeof für benutzerdefinierte Strukturen verwenden, die keine Felder oder Eigenschaften enthalten, die Referenztypen sind, oder Marshal.SizeOf(Type) oder Marshal.SizeOf(object) , um die nicht verwaltete Größe von a zu erhalten Typ oder eine Struktur, die ein sequenzielles oder explizites layout .



0. Für den Beispielcode:

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

1. Demonstrations-Struktur

[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. Subtrahieren von verwalteten Zeigern:

/// 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. Offenes internes Strukturlayout anzeigen:

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. Ergebnisse und Diskussion

Die StructLayout(..., Pack) kann zur Deklaration der struct T mit einem der folgenden Werte hinzugefügt werden: {0, 1, 2, 4, 8, 16, 32, 64, 128} . Der Standardwert, wenn Pack nicht angegeben ist - oder äquivalent zu Pack=0 setzt das Packing gleich IntPtr.Size ( 4 auf x86, 8 auf x64).

Die Ergebnisse des Ausführen des obigen Programms zeigen, dass der Pack Wert nur die Marshalling-Größe betrifft, die von Marshal.SizeOf gemeldet wird, und nicht die tatsächliche Größe eines einzelnen T Speicherabbilds, das als Byte-Offset zwischen physisch benachbarten Instanzen angenommen wird. Der Testcode misst dies über das diagnostizierte Array new T[2] , das rgt zugeordnet ist .

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

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

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

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

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

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

Wie bereits erwähnt, finden wir pro Architektur ( x86 , x64 ), das verwaltete Feldlayout ist unabhängig von der Pack Einstellung konsistent. Hier sind die tatsächlich verwalteten Feldoffsets, wiederum für den 32- und 64-Bit-Modus, wie im obigen Code angegeben:

┌─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

Das Wichtigste in dieser Tabelle ist, dass (wie von Hans erwähnt ) die gemeldeten Feldoffsets in Bezug auf ihre Deklarationsreihenfolge nicht monoton sind. Die Felder von ValueType Instanzen werden immer neu angeordnet, sodass alle Felder mit Referenztypen zuerst ValueType werden. Wir können sehen, dass das String Feld d 0 ist.

Eine weitere Neuordnung optimiert die Feldreihenfolge, um einen internen Polsterüberschuß zu teilen, der andernfalls verschwendet würde. Wir können dies mit dem byte Feld b sehen , das von dem zweiten deklarierten Feld wegbewegt wurde, bis es das letzte ist.

Natürlich können wir durch Sortieren der Zeilen der vorherigen Tabelle das wahre intern verwaltete Layout eines .NET ValueType . Beachten Sie, dass wir dieses Layout erhalten können, obwohl die Beispielstruktur T eine verwaltete Referenz ( String d ) enthält und daher nicht blittierbar ist :

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

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

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

Zuvor haben wir die Größe einer einzelnen verwalteten Strukturinstanz ermittelt, indem wir die Byte-Offset-Differenz zwischen benachbarten Instanzen berechnet haben. Unter Berücksichtigung dieser Tatsache zeigen die letzten Zeilen der vorherigen Tabelle trivial das Auffüllen, das die CLR intern auf das Ende der Beispielstruktur T anwendet. Erinnern Sie sich noch einmal daran, dass dieses interne Padding von der CLR festgelegt wurde und völlig außerhalb unserer Kontrolle liegt.

5. Coda
Der Vollständigkeit halber zeigt diese letzte Tabelle die Menge an Auffüllung, die während des Marshalling während der Übertragung synthetisiert wird. Beachten Sie, dass diese Marshal Füllung in einigen Fällen negativ gegenüber der internen verwalteten Größe ist. Obwohl die interne verwaltete Größe einer T in x64 beispielsweise 24 Byte beträgt, kann die vom Marshalling ausgegebene Struktur beispielsweise 19 oder 20 Byte mit Pack=1 oder Pack=2 .

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


Sie können entweder den Operator SizeOf oder die SizeOf Funktion verwenden.
Es gibt einige Unterschiede zwischen diesen Optionen. Weitere Informationen finden Sie in den Referenzlinks.

Eine gute Möglichkeit, diese Funktion zu verwenden, ist eine generische Methode oder eine ähnliche Erweiterungsmethode:

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






byte