Как проверить количество байтов, потребляемых структурой? - PullRequest
39 голосов
/ 29 июля 2010

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

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

Ответы [ 8 ]

102 голосов
/ 29 июля 2010

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

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

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

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

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

30 голосов
/ 29 июля 2010

Вы можете использовать либо оператор 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);
  }
}
9 голосов
/ 29 июля 2010

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

6 голосов
/ 14 ноября 2012

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

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

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

4 голосов
/ 23 декабря 2017

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 на x86, 8 на x64).

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

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

Как уже отмечалось, мы находим, что для каждой архитектуры ( 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) и, следовательно, non-blittable :

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

Ранее мы определяли размер отдельного экземпляра управляемой структуры, вычисляя разницу смещения байтов между соседними экземплярами.Принимая это во внимание, последние строки предыдущей таблицы тривиально показывают заполнение, которое CLR внутренне применяет к концу примера struct 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 … 20 4/8/16/32/64… 5 19 … 24

4 голосов
/ 16 июня 2016

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

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

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

3 голосов
/ 29 июля 2010

Вы хотите использовать 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);
}
2 голосов
/ 29 июля 2010

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

...