I always understood structs (value types) contain exactly the number of bytes as defined in the fields of the structure... however, I did some tests and there seems to be an exception for the empty structs:
我总是理解结构(值类型)包含结构字段中定义的字节数...但是,我做了一些测试,并且空结构似乎有一个例外:
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;
}
}
If you run the program, you'll find that en FooStruct which has obviously 0 bytes of data will consume 1 byte of memory. The reason this is a problem for me is that I want Bar<FooStruct>
to consume exactly 4 bytes (because I'm going to allocate it a lot).
如果你运行程序,你会发现显然有0字节数据的en FooStruct将消耗1个字节的内存。这对我来说是个问题的原因是我希望Bar
Why does it have this behavior and is there a way to fix this (e.g. is there a special thing that consumes 0 bytes-- I'm not looking for a redesign)?
为什么它有这种行为,有没有办法解决这个问题(例如,是否存在消耗0字节的特殊事物 - 我不是在寻找重新设计)?
3 个解决方案
#1
9
Summary: An empty struct in .NET consumes 1 byte. You can think of this as packing
, since the unnamed byte is only accessible via unsafe code.
简介:.NET中的空结构占用1个字节。您可以将此视为打包,因为未命名的字节只能通过不安全的代码访问。
More information: if you do all your pointer arithmetic according to values reported by .NET, things work out consistently.
更多信息:如果你根据.NET报告的值执行所有指针算术,那么事情就会一致。
The following example illustrates using adjacent 0-byte structures on the stack, but these observations obviously apply to arrays of 0-byte structures as well.
以下示例说明了在堆栈上使用相邻的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
}
As I mentioned in a comment, @supercat got it right when he noted, "There probably wouldn't have been any problem with designing .NET to allow for zero-length structures from the beginning, but there could be some things that would break if it were to start doing so now."
正如我在评论中提到的那样,当@supercat指出,“设计.NET从一开始就允许零长度结构可能没有任何问题,但可能会有一些东西会破坏如果它现在开始这样做。“
EDIT: If you need to programmatically distinguish between 0-byte vs. 1-byte value types, you can use the following:
编辑:如果您需要以编程方式区分0字节和1字节值类型,您可以使用以下内容:
public static bool IsZeroSizeStruct(Type t)
{
return t.IsValueType && !t.IsPrimitive &&
t.GetFields((BindingFlags)0x34).All(fi => IsZeroSizeStruct(fi.FieldType));
}
Note that this correctly identifies arbitrarily nested structs where the total size would be zero.
请注意,这可以正确识别任意嵌套的结构,其中总大小为零。
[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]
What's strange here is that, when nesting 0-byte structs, the single-byte minimum can accumulate (i.e. into 3 bytes for 'zz' and 'zzz') but then suddenly all of that chaff disappears as soon as a single "substantial" field is included.
[编辑:参见注释]这里奇怪的是,当嵌套0字节结构时,单字节最小值可以累积(即'zz'和'zzz'为3个字节)但突然之间所有的chaff都会消失作为单个“实质”字段包括在内。
#2
9
It's the same reason zero-sized objects aren't allowed in C (or C++): pointer arithmetic in terms of number of elements.
这与C(或C ++)中不允许零大小的对象的原因相同:指针算术的元素数量。
C# supports pointer subtraction in unsafe blocks, defined thus:
C#支持不安全块中的指针减法,因此定义如下:
Given two expressions,
P
andQ
, of a pointer typeT*
, the expressionP – Q
computes the difference between the addresses given byP
andQ
and then divides that difference bysizeof(T)
.给定指针类型T *的两个表达式P和Q,表达式P-Q计算由P和Q给出的地址之间的差值,然后将该差值除以sizeof(T)。
Since division by zero is not possible, this implies that sizeof(T) > 0
for all T
.
由于无法除以零,这意味着所有T的sizeof(T)> 0。
#3
0
Is this what you are looking for?
这是你想要的?
Null / Empty value for a struct in .Net 1.x
.Net 1.x中结构的空/空值
This solution mentions not having any overhead, which I believe is what you are looking for.
这个解决方案提到没有任何开销,我相信这是你正在寻找的。
Furthermore, Stroustrup talks about why structs aren't empty in C++, now the language is different, but the principle is the same: http://www.stroustrup.com/bs_faq2.html#sizeof-empty
此外,Stroustrup谈到为什么结构体在C ++中不是空的,现在语言不同,但原理是相同的:http://www.stroustrup.com/bs_faq2.html#sizeof-empty
#1
9
Summary: An empty struct in .NET consumes 1 byte. You can think of this as packing
, since the unnamed byte is only accessible via unsafe code.
简介:.NET中的空结构占用1个字节。您可以将此视为打包,因为未命名的字节只能通过不安全的代码访问。
More information: if you do all your pointer arithmetic according to values reported by .NET, things work out consistently.
更多信息:如果你根据.NET报告的值执行所有指针算术,那么事情就会一致。
The following example illustrates using adjacent 0-byte structures on the stack, but these observations obviously apply to arrays of 0-byte structures as well.
以下示例说明了在堆栈上使用相邻的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
}
As I mentioned in a comment, @supercat got it right when he noted, "There probably wouldn't have been any problem with designing .NET to allow for zero-length structures from the beginning, but there could be some things that would break if it were to start doing so now."
正如我在评论中提到的那样,当@supercat指出,“设计.NET从一开始就允许零长度结构可能没有任何问题,但可能会有一些东西会破坏如果它现在开始这样做。“
EDIT: If you need to programmatically distinguish between 0-byte vs. 1-byte value types, you can use the following:
编辑:如果您需要以编程方式区分0字节和1字节值类型,您可以使用以下内容:
public static bool IsZeroSizeStruct(Type t)
{
return t.IsValueType && !t.IsPrimitive &&
t.GetFields((BindingFlags)0x34).All(fi => IsZeroSizeStruct(fi.FieldType));
}
Note that this correctly identifies arbitrarily nested structs where the total size would be zero.
请注意,这可以正确识别任意嵌套的结构,其中总大小为零。
[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]
What's strange here is that, when nesting 0-byte structs, the single-byte minimum can accumulate (i.e. into 3 bytes for 'zz' and 'zzz') but then suddenly all of that chaff disappears as soon as a single "substantial" field is included.
[编辑:参见注释]这里奇怪的是,当嵌套0字节结构时,单字节最小值可以累积(即'zz'和'zzz'为3个字节)但突然之间所有的chaff都会消失作为单个“实质”字段包括在内。
#2
9
It's the same reason zero-sized objects aren't allowed in C (or C++): pointer arithmetic in terms of number of elements.
这与C(或C ++)中不允许零大小的对象的原因相同:指针算术的元素数量。
C# supports pointer subtraction in unsafe blocks, defined thus:
C#支持不安全块中的指针减法,因此定义如下:
Given two expressions,
P
andQ
, of a pointer typeT*
, the expressionP – Q
computes the difference between the addresses given byP
andQ
and then divides that difference bysizeof(T)
.给定指针类型T *的两个表达式P和Q,表达式P-Q计算由P和Q给出的地址之间的差值,然后将该差值除以sizeof(T)。
Since division by zero is not possible, this implies that sizeof(T) > 0
for all T
.
由于无法除以零,这意味着所有T的sizeof(T)> 0。
#3
0
Is this what you are looking for?
这是你想要的?
Null / Empty value for a struct in .Net 1.x
.Net 1.x中结构的空/空值
This solution mentions not having any overhead, which I believe is what you are looking for.
这个解决方案提到没有任何开销,我相信这是你正在寻找的。
Furthermore, Stroustrup talks about why structs aren't empty in C++, now the language is different, but the principle is the same: http://www.stroustrup.com/bs_faq2.html#sizeof-empty
此外,Stroustrup谈到为什么结构体在C ++中不是空的,现在语言不同,但原理是相同的:http://www.stroustrup.com/bs_faq2.html#sizeof-empty