Ответ 1
Сводка. Пустая структура в .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
[edit: see comment] Что странно в том, что при вложенных 0-байтовых структурах однобайтовый минимум может накапливаться (т.е. в 3 байта для "zz" и "zzz" ), но затем внезапно все эта мякина исчезает, как только включается одно "существенное" поле.