2013-05-17 2 views
11

Я всегда понимал, что структуры (типы значений) содержат точно количество байтов, как определено в полях структуры ... однако я сделал несколько тестов и, похоже, есть исключение для пустых структур:Почему пустая структура в C# потребляет память

public class EmptyStructTest 
{ 
    static void Main(string[] args) 
    { 
     FindMemoryLoad<FooStruct>((id) => new FooStruct()); 
     FindMemoryLoad<Bar<FooStruct>>((id) => new Bar<FooStruct>(id)); 
     FindMemoryLoad<Bar<int>>((id) => new Bar<int>(id)); 
     FindMemoryLoad<int>((id) => id); 
     Console.ReadLine(); 
    } 

    private static void FindMemoryLoad<T>(Func<int, T> creator) where T : new() 
    { 
     GC.Collect(GC.MaxGeneration); 
     GC.WaitForFullGCComplete(); 
     Thread.MemoryBarrier(); 
     long start = GC.GetTotalMemory(true); 

     T[] ids = new T[10000]; 
     for (int i = 0; i < ids.Length; ++i) 
     { 
      ids[i] = creator(i); 
     } 

     long end = GC.GetTotalMemory(true); 
     GC.Collect(GC.MaxGeneration); 
     GC.WaitForFullGCComplete(); 
     Thread.MemoryBarrier(); 

     Console.WriteLine("{0} {1}", ((double)end-start)/10000.0, ids.Length); 
    } 

    public struct FooStruct { } 

    public struct Bar<T> where T : struct 
    { 
     public Bar(int id) { value = id; thing = default(T); } 

     public int value; 
     public T thing; 
    } 
} 

Если вы запустите программу, вы увидите, что ан FooStruct, который, очевидно, 0 байт данных будет потреблять 1 байт памяти. Причина этого в том, что я хочу, чтобы Bar<FooStruct> потреблял ровно 4 байта (потому что я собираюсь выделить его много).

Почему у этого есть это поведение, и есть ли способ исправить это (например, есть ли что-то особенное, которое потребляет 0 байт - я не ищу редизайн)?

+0

P.S .: [StructLayout (LayoutKind.Explicit, Size = 0)] дает тот же результат. – atlaste

+1

Является ли GC.GetTotalMemory точным? Если так, я потратил деньги на профайлер памяти. – Paparazzi

+0

Почему бы вам просто не пропустить Bar и включить FooStruct значение public int ;? – Paparazzi

ответ

8

Резюме: пустая структура в .NET потребляет 1 байт. Вы можете думать об этом как packing, так как безымянный байт доступен только через небезопасный код.

Дополнительная информация: если вы выполняете всю свою арифметику указателя в соответствии со значениями, указанными .NET, все работает последовательно.

Следующий пример иллюстрирует использование смежных 0-байтовых структур в стеке, но эти наблюдения, очевидно, применимы и к массивам 0-байтовых структур.

struct z { }; 

unsafe static void foo() 
{ 
    var z3 = default(z); 
    bool _; 
    long cb_pack, Δz, cb_raw; 
    var z2 = default(z); // (reversed since stack offsets are negative) 
    var z1 = default(z); 
    var z0 = default(z); 

    // stack packing differs between x64 and x86 
    cb_pack = (long)&z1 - (long)&z0; // --> 1 on x64, 4 on x86 

    // pointer arithmetic should give packing in units of z-size 
    Δz = &z1 - &z0; // --> 1 on x64, 4 on x86 

    // if one asks for the value of such a 'z-size'... 
    cb_raw = Marshal.SizeOf(typeof(z));  // --> 1 

    // ...then the claim holds up: 
    _ = cb_pack == Δz * cb_raw;  // --> true 

    // so you cannot rely on special knowledge that cb_pack==0 or cb_raw==0 
    _ = &z0 /* + 0 */ == &z1; // --> false 
    _ = &z0 /* + 0 + 0 */ == &z2; // --> false 

    // instead, the pointer arithmetic you meant was: 
    _ = &z0 + cb_pack == &z1; // --> true 
    _ = &z0 + cb_pack + cb_pack == &z2; // --> true 

    // array indexing also works using reported values 
    _ = &(&z0)[Δz] == &z1; // --> true 

    // the default structure 'by-value' comparison asserts that 
    // all z instances are (globally) equivalent... 
    _ = EqualityComparer<z>.Default.Equals(z0, z1); // --> true 

    // ...even when there are intervening non-z objects which 
    // would prevent putative 'overlaying' of 0-sized structs: 
    _ = EqualityComparer<z>.Default.Equals(z0, z3); // --> true 

    // same result with boxing/unboxing 
    _ = Object.Equals(z0, z3); // -> true 

    // this one is never true for boxed value types 
    _ = Object.ReferenceEquals(z0, z0); // -> false 
} 

Как я уже говорил в комментариях, @supercat получил это право, когда он заметил, «Там, вероятно, не было бы никаких проблем с проектированием .NET для обеспечения нулевой длиной структур с самого начала, но там может быть, некоторые вещи, которые сломались бы, если бы теперь начали делать это ».

EDIT: Если необходимо программно различать 0 байт типов значений по сравнению с 1-байт, вы можете использовать следующее:

public static bool IsZeroSizeStruct(Type t) 
{ 
    return t.IsValueType && !t.IsPrimitive && 
      t.GetFields((BindingFlags)0x34).All(fi => IsZeroSizeStruct(fi.FieldType)); 
} 

Обратите внимание, что это правильно идентифицирует произвольно вложенными где Структуры общее размер будет равен нулю.

[StructLayout(LayoutKind.Sequential)] 
struct z { }; 
[StructLayout(LayoutKind.Sequential)] 
struct zz { public z _z, __z, ___z; }; 
[StructLayout(LayoutKind.Sequential)] 
struct zzz { private zz _zz; }; 
[StructLayout(LayoutKind.Sequential)] 
struct zzzi { public zzz _zzz; int _i; }; 

/// ... 

c = Marshal.SizeOf(typeof(z));  // 1 
c = Marshal.SizeOf(typeof(zz));  // 3 
c = Marshal.SizeOf(typeof(zzz)); // 3 
c = Marshal.SizeOf(typeof(zzzi)); // 8 

_ = IsZeroSizeStruct(typeof(z)); // true 
_ = IsZeroSizeStruct(typeof(zz)); // true 
_ = IsZeroSizeStruct(typeof(zzz)); // true 
_ = IsZeroSizeStruct(typeof(zzzi)); // false 

[править: см комментарий] Что странно здесь является то, что, когда вложенности 0 байт структур, единственный байт минимум может накапливаться (т.е. в 3 байта для «ZZ» и «ZZZ»), но затем внезапно вся эта мякина исчезает, как только включается одно «существенное» поле.

+0

Что значит «пропасть пропадает»? 'zzzi' - 8 байтов, из которых 4 являются« существенным полем ». Прокладка не исчезла, она была дополнена * больше *, чтобы правильно совместить 'int'. –

+0

@BenVoigt Спасибо, ты прав. Я думаю, что, возможно, ошибочно считал, что 'int _i' был« длинным »здесь, поэтому занимал все 8 байтов, о которых сообщает' sizeof zzzi'. Это было бы загадочно. Я обновил свой пост. –

0

Вы хотите что-то ищите?

Null/Empty value for a struct in .Net 1.x

Это решение затрагивает не имея каких-либо накладных расходов, которые я считаю, это то, что вы ищете.

Кроме того, Страуструп говорит о том, почему не Структуры пусты в C++, теперь язык отличается, но принцип тот же: http://www.stroustrup.com/bs_faq2.html#sizeof-empty

+1

В C++ предполагается, что каждый объект имеет отдельный идентификатор, инкапсулированный его адресом. Таким образом, каждый объект должен сделать адрес, присвоенный ему недоступным для любого другого объекта. Самый простой способ сделать это - сделать каждый объект минимальным для одного адресуемого устройства. Я не думаю, что такая вещь должна быть необходима в .NET, так как я не знаю какой-либо парадигмы сравнения ссылок на равенство. – supercat

+0

@supercat Я тоже об этом подумал ... но, возможно, это имеет смысл - в конце концов, как вы должны писать итераторы и т. Д. В небезопасном коде, если размер действительно равен 0. – atlaste

+0

@kirk извините, вы решение в этом сценарии просто неверно. Джон прав, хотя, вы должны прочитать его комментарий и его вопрос снова. – atlaste

8

По той же причине объекты с нулевым размером не допускаются в C (или C++): арифметика указателя по количеству элементов.

С # поддерживает указатель вычитание в небезопасных блоках, определенных следующим образом:

Даны два выражения, P и Q, типа указателя T*, выражение P – Q вычисляет разность между адресами заданных P и Q и затем делит эту разницу на sizeof(T).

Поскольку деление на ноль невозможно, это означает, что sizeof(T) > 0 для всех T.

Смежные вопросы