为什么C#中的空结构会消耗内存
我一直理解结构(值类型)包含的字节数与结构字段中定义的字节数完全相同。。。但是,我做了一些测试,空结构似乎有一个例外:为什么C#中的空结构会消耗内存,c#,.net,generics,memory-management,value-type,C#,.net,Generics,Memory Management,Value Type,我一直理解结构(值类型)包含的字节数与结构字段中定义的字节数完全相同。。。但是,我做了一些测试,空结构似乎有一个例外: public class EmptyStructTest { static void Main(string[] args) { FindMemoryLoad<FooStruct>((id) => new FooStruct()); FindMemoryLoad<Bar<FooStruct>&g
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;
}
}
公共类清空结构测试
{
静态void Main(字符串[]参数)
{
FindMemoryLoad((id)=>newFooStruct());
FindMemoryLoad((id)=>新条(id));
FindMemoryLoad((id)=>新条(id));
FindMemoryLoad((id)=>id);
Console.ReadLine();
}
私有静态void FindMemoryLoad(Func creator),其中T:new()
{
GC.Collect(GC.MaxGeneration);
GC.WaitForFullGCComplete();
Thread.MemoryBarrier();
长启动=GC.GetTotalMemory(true);
T[]ids=新的T[10000];
对于(int i=0;i
如果您运行该程序,您会发现显然有0字节数据的en FooStruct将消耗1字节的内存。这对我来说是个问题的原因是我希望Bar
正好消耗4个字节(因为我要分配很多字节)
它为什么会有这种行为?有没有办法解决这个问题(例如,是否有一个特殊的东西消耗0字节——我不想重新设计)?这就是您要找的吗 这个解决方案提到没有任何开销,我相信这就是您想要的
此外,Stroustrup还讨论了C++中结构为何不空,现在语言不同,但原理是相同的:
< Stime> Cuthy>:.NET中的空结构消耗了1字节。您可以将其视为打包,因为未命名字节只能通过不安全的代码访问
更多信息:如果您根据.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
}
请注意,这可以正确标识总大小为零的任意嵌套结构
[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字节结构时,单个字节的最小值可以累积(即,对于'zz'和'zzz'可以累积为3个字节),但一旦包含一个“实质性”字段,所有这些杂碎就会突然消失。这也是C(或C++)中不允许使用零大小对象的原因:按元素数计算的指针算法 C#支持不安全块中的指针减法,定义如下: 给定指针类型
T*
的两个表达式p
和Q
,表达式p–Q
计算p
和Q
给出的地址之间的差值,然后将该差值除以sizeof(T)
由于不可能被零除,这意味着
sizeof(T)>0
对于所有T
p.S.:[StructLayout(LayoutKind.Explicit,Size=0)]给出了相同的结果。GC.GetTotalMemory是否准确?如果是这样的话,我就在内存分析器上浪费了钱。为什么不跳过Bar,让FooStruct包含公共int值@布拉姆,不,这是不准确的:。允许值有0个字节会产生吞噬黑洞的奇点。如果数组是一个明确的牺牲品,那么它的所有元素都将具有相同的地址。用一个角式的例子来测试这一点,测试你所关心的结构。在C++中,每个对象都被假定为一个由它的地址封装的不同的身份。因此,每个对象都必须使分配给它的地址对任何其他对象都不可用。实现这一点的最简单方法是使每个对象至少占用一个可寻址单元。我认为这样的事情在.NET中是不必要的,因为我不知道任何比较byrefs是否相等的范例。@supercat我也想过这个。。。但也许这是有道理的——毕竟,如果大小真的为0,那么在不安全的代码中应该如何编写迭代器等等。@kirk抱歉,您的解决方案在这种情况下是完全错误的。Jon是对的,你应该再次阅读他的评论和他的问题。@Stefandbruijn:我不知道有任何论点会迫使我在.net的设计中强制使用最小大小的结构。由于没有“仅原语”泛型类型参数,我看不出代码如何在不知道底层类型的情况下尝试循环通过固定数组。此外,即使是编写代码以使用byrefs对固定数组中的连续元素重复调用方法,在.net中的每个数组都有一个基于项的长度,该长度不依赖于其物理分配<代码>用于(i=0,ptr=array_数据;i@StefandeBruijn:…即使array\u item\u size
为零,也可以正常工作。ptr
值不会出现在任何地方,但那又怎么样?也许可以说,比较某种类型指针的能力意味着该类型必须具有非零大小,但我不确定何时真的需要进行比较指向未知或泛型类型的指针。你说“颖壳消失”是什么意思?zzzi
是8个字节,其中只有4个是“实体字段”.填充物没有消失,它被填充得更多以正确对齐int
@BenVoigt谢谢,你是对的。我想我可能会错误地认为int\u I
在这里是一个长的,
[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