15

構造体(値型)には、構造体のフィールドで定義されている正確なバイト数が含まれていることを常に理解していました...ただし、いくつかのテストを行ったところ、空の構造体には例外があるようです:

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;
    }
}

プログラムを実行すると、明らかに 0 バイトのデータを持つ FooStruct が 1 バイトのメモリを消費することがわかります。これが私にとって問題である理由は、Bar<FooStruct>正確に 4 バイトを消費したいからです (大量に割り当てるため)。

なぜこのような動作をするのでしょうか? また、これを修正する方法はありますか?

4

3 に答える 3

11

概要: .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 は、「最初から長さ 0 の構造を許可するように .NET を設計することにはおそらく問題はなかったでしょうが、壊れる可能性があるものもあるかもしれません」と述べたとき、それは正しかった。もし今それを始めたとしたら。」

編集: 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 バイトの構造体をネストすると、1 バイトの最小値が (つまり、'zz' と 'zzz' の場合は 3 バイトに) 蓄積される可能性があるが、突然、そのもみ殻のすべてがすぐに消えることです。単一の「実質的な」フィールドが含まれているためです。

于 2015-01-08T23:45:28.343 に答える
0

これはあなたが探しているものですか?

.Net 1.x の構造体の Null/空の値

このソリューションには、オーバーヘッドがないことが記載されています。これは、あなたが探しているものだと思います。

さらに、Stroustrup は、C++ で構造体が空でない理由について語っています。言語は異なりますが、原則は同じです: http://www.stroustrup.com/bs_faq2.html#sizeof-empty

于 2013-05-17T14:43:51.340 に答える