Как проверить количество байтов, потребляемых структурой?

Если я создаю относительно большую структуру, как я могу вычислить байты, которые она занимает в памяти?

Мы можем сделать это вручную, но если структура достаточно велика, то как нам это сделать? Есть ли какой-то кусок кода или приложение?

8 ответов

Решение

Вы можете использовать либо sizeof оператор или SizeOf функция.
Есть некоторые различия между этими опциями, см. Ссылки для получения дополнительной информации.

В любом случае, хороший способ использовать эту функцию - использовать универсальный метод или метод расширения, например:

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

Структуры были неприятными животными в компьютерной инженерии в течение очень долгого времени. Их расположение памяти очень зависит от оборудования. Чтобы сделать их эффективными, их элементы должны быть выровнены, чтобы процессор мог быстро считывать и записывать их значения без необходимости мультиплексировать байты в соответствии с шириной шины памяти. У каждого компилятора есть своя собственная стратегия упаковки членов, часто направляемая, например, директивой #pragma pack в программе на C или C++.

Что нормально, а скорее проблема в сценариях взаимодействия. Где один кусок кода может делать другие предположения о структуре структуры, чем другой кусок, скомпилированный другим компилятором. Вы можете увидеть это в COM, дедушкином решении.NET для взаимодействия программирования. COM имеет очень плохую поддержку для обработки структур. Он не поддерживает их как собственный тип автоматизации, но имеет обходной путь через интерфейс IRecordInfo. Это позволяет программе обнаруживать структуру памяти во время выполнения посредством явного объявления структуры в библиотеке типов. Что работает хорошо, но довольно неэффективно.

Дизайнеры.NET приняли очень смелое и правильное решение для решения этой проблемы. Они сделали структуру памяти структуры полностью недоступной для обнаружения. Не существует документированного способа получить смещение члена. И, как следствие, нет способа узнать размер структуры. Любимый ответ каждого, используйте Marshal.SizeOf() на самом деле не является решением. Это возвращает размер структуры после ее маршалинга, размер, который необходимо передать, скажем, Marshal.AllocCoTaskMem() перед вызовом Marshal.StructureToPtr. Это упорядочивает и выравнивает элементы структуры в соответствии с атрибутом [StructLayout], который связан со структурой. Обратите внимание, что этот атрибут не требуется для структур (как и для классов), среда выполнения реализует стандарт по умолчанию, который использует объявленный порядок для членов.

Одним очень приятным побочным эффектом отсутствия макета является то, что CLR может с ним подшутить. При упаковке и выравнивании элементов структуры в макете могут появиться дыры, которые не хранят никаких данных. Вызывается заполнение байтов. Учитывая, что компоновка не обнаруживаема, CLR может фактически использовать заполнение. Он перемещает член, если он достаточно мал, чтобы соответствовать такой дыре. Теперь вы фактически получите структуру, размер которой меньше, чем обычно требуется, учитывая объявленную структуру структуры. И, в частности, Marshal.SizeOf() вернет неправильное значение для размера структуры, оно возвращает слишком большое значение.

Короче говоря, нет общего способа получить точное значение размера структуры программно. Лучше всего просто не задавать вопрос. Marshal.SizeOf() даст вам приблизительную оценку, если предположить, что структура является blittable. Если по какой-то причине вам нужно точное значение, вы можете посмотреть на сгенерированный машинный код метода, который объявляет локальную переменную типа структуры, и сравнить его с тем же методом без этой локальной переменной. Вы увидите разницу в настройке указателя стека, инструкции "sub esp, xxx" в верхней части метода. Конечно, это будет зависеть от архитектуры, обычно вы получите большую структуру в 64-битном режиме.

Вы можете использовать sizeof() ключевое слово для пользовательских структур, которые не содержат полей или свойств, которые являются ссылочными типами, или используют Marshal.SizeOf(Type) или же Marshal.SizeOf(object) чтобы получить неуправляемый размер типа или структуры, которая имеет последовательную или явную разметку.

0. Для примера кода:

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

1. Демонстрационная структура

[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. Вычитание управляемых указателей:

/// 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. Выявить управляемую структуру внутренней структуры:

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. Результаты и обсуждение

StructLayout(..., Pack) настройки могут быть добавлены к объявлению struct T с любым из следующих значений: {0, 1, 2, 4, 8, 16, 32, 64, 128}. Значение по умолчанию, когда Pack не указано или эквивалентно Pack=0- устанавливает упаковку равной IntPtr.Size (4 на х86, 8 на х64).

Результаты выполнения вышеуказанной программы показывают, что Pack значение влияет только на размер маршалинга Marshal.SizeOf, а не фактический размер одного T образ памяти, предполагаемый как смещение байтов между физически смежными экземплярами. Тестовый код измеряет это с помощью диагностического управляемого массива new T[2] назначен на ргт.

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

Как уже отмечалось, мы находим, что для каждой архитектуры (x86, x64) структура управляемого поля согласована независимо от Pack установка. Вот фактические смещения управляемого поля, опять же как для 32-, так и для 64-битного режима, как показано в коде выше:

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

Самая важная вещь, которую следует отметить в этой таблице, - то, что (как упомянуто Хансом), сообщенные смещения полей являются немонотонными относительно их порядка объявления. Поля ValueType экземпляры всегда переупорядочиваются так, что все поля ссылочного типа идут первыми. Мы видим, что String поле d имеет смещение 0.

Дальнейшее переупорядочение оптимизирует порядок полей для того, чтобы разделить избыточный внутренний отступ, который в противном случае был бы потрачен впустую. Мы можем увидеть это с byte поле b, которое было перемещено из второго объявленного поля, вплоть до последнего.

Естественно, сортируя строки предыдущей таблицы, мы можем выявить истинную внутреннюю управляемую компоновку .NET ValueType, Обратите внимание, что мы можем получить этот макет, несмотря на пример структуры T содержащий управляемую ссылку (String d) и, таким образом, быть неблизким:

============= x86 ========================= x64 ============
field type size offs endfield 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 … 16internal padding: 5 19 … 24

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

Ранее мы определяли размер отдельного экземпляра управляемой структуры, вычисляя разницу смещения байтов между соседними экземплярами. Принимая это во внимание, последние строки предыдущей таблицы тривиально показывают заполнение, которое CLR внутренне применяет к концу примера структуры T, Помните еще раз, конечно, что это внутреннее заполнение фиксируется CLR и полностью вне нашего контроля.

5. Кода
Для полноты этой последней таблицы показано количество заполнения, которое будет синтезироваться на лету во время маршалинга. Обратите внимание, что в некоторых случаях это Marshal заполнение отрицательно по сравнению с внутренним управляемым размером. Например, хотя внутренний управляемый размер T в x64 это 24 байта, структура, испускаемая маршалингом, может быть 19 или 20 байтов с Pack=1 или же 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 … 204/8/16/32/64… 5 19 … 24

Я написал крошечную маленькую библиотеку на CIL (ассемблере .NET), чтобы показать некоторые полезные функции, которые недоступны в C#. Я вырвался sizeof инструкция.

Это значительно отличается от sizeof оператор в C#. По сути, он получает размер структуры (или ссылочного типа, что смешно с некоторыми оптимизациями), включая отступы и все. Итак, если вы должны были создать массив T, затем вы можете использовать size of для определения расстояния между каждым элементом массива в байтах. Это также полностью проверяемый и управляемый код. Обратите внимание, что в Mono была ошибка (до 3.0?), Которая приводила к неправильному сообщению о размере ссылочных типов, что распространялось на структуры, содержащие ссылочные типы.

В любом случае, вы можете скачать лицензионную библиотеку BSD (и CIL) из BitBucket. Вы также можете увидеть пример кода и некоторые подробности в моем блоге.

В .NET Coresizeof CIL инструкция была выставлена ​​через недавно добавленные Unsafe учебный класс. Добавить ссылку на System.Runtime.CompilerServices.Unsafe пакет и просто сделать это:

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

Он работает и для ссылочных типов (вернет 4 или 8 в зависимости от архитектуры вашего компьютера).

Вы хотите использовать 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);
}

Ты можешь использовать System.Runtime.InteropServices.Marshal.SizeOf() чтобы получить размер в байтах.

Другие вопросы по тегам