代码之家  ›  专栏  ›  技术社区  ›  Simsons

如何检查结构消耗的字节数?

  •  39
  • Simsons  · 技术社区  · 14 年前

    我们可以手动执行,但是如果结构足够大,那么如何执行呢?是否有一些代码块或应用程序?

    8 回复  |  直到 8 年前
        1
  •  31
  •   Shimmy Weitzhandler 500 - Internal Server Error    13 年前

    你可以用 sizeof 操作员或 SizeOf 功能。

    无论如何,使用该函数的一个好方法是使用一个泛型方法或扩展方法,如下所示:

    static class Test
    {
      static void Main()
      {
        //This will return the memory usage size for type Int32:
        int size = SizeOf<Int32>();
    
        //This will return the memory usage size of the variable 'size':
        //Both lines are basically equal, the first one makes use of ex. methods
        size = size.GetSize();
        size = GetSize(size);
      }
    
      public static int SizeOf<T>()
      {
        return System.Runtime.InteropServices.Marshal.SizeOf(typeof(T));
      }
    
      public static int GetSize(this object obj)
      {
        return System.Runtime.InteropServices.Marshal.SizeOf(obj);
      }
    }
    
        2
  •  111
  •   Hans Passant    14 年前

    很长一段时间以来,结构一直是计算机工程中的麻烦动物。它们的内存布局非常依赖于硬件。为了提高效率,它们的成员必须对齐,这样CPU就可以快速地读写它们的值,而不必对字节进行多路复用以适应内存总线的宽度。每个编译器都有自己的成员打包策略,通常是由C或C++程序中的“γ-PrimaMax包”指令来指导的。

    这没关系,但在互操作场景中是个问题。其中一段代码可能对结构布局做出不同的假设,而另一段代码则由不同的编译器编译。您可以在COM.NET的互操作编程解决方案中看到这一点。COM有 非常 对处理结构的支持较差。它不支持将它们作为本机自动化类型,但通过IRecordInfo接口有一个解决方法。它允许程序在运行时通过类型库中结构的显式声明来发现内存布局。它工作正常,但效率很低。

    NET的设计者们做出了一个非常勇敢、正确的决定来解决这个问题。他们使结构的内存布局完全不可发现。没有文档化的方法来检索成员的偏移量。扩展一下,没有办法发现结构的大小。大家最喜欢的答案是使用Marshal.SizeOf()实际上并不是解决方案。返回struct的大小 在它被编组之后 ,在调用Marshal.StructureToPtr之前,需要传递给Marshal.allocTaskMem()的大小。它根据与结构关联的[StructLayout]属性排列和对齐结构成员。注意,这个属性对于结构不是必需的(就像对于类一样),运行时实现了一个默认的属性,它使用成员声明的顺序。

    较小 错误的 对于结构大小,它返回的值太大。

    长话短说,没有通用的方法可以通过编程获得结构大小的准确值。最好不要问这个问题。SizeOf()会给你一个猜测,假设结构是blittable的。如果出于某种原因需要准确的值,那么可以查看声明结构类型的局部变量的方法的生成的机器代码,并将其与没有该局部变量的相同方法进行比较。您将看到堆栈指针调整的不同之处,即方法顶部的“sub esp,xxx”指令。当然,它将依赖于体系结构,您通常会在64位模式下获得更大的结构。

        3
  •  9
  •   Allon Guralnek    14 年前

    你可以用 sizeof() 不包含任何引用类型的字段或属性的用户定义结构的关键字,或使用 Marshal.SizeOf(Type) Marshal.SizeOf(object) 获取具有顺序或显式 layout .

        4
  •  7
  •   Peter Mortensen icecrime    12 年前

    CIL .NET 的汇编语言)来公开一些在C#中不可用的简洁功能。我打破了 sizeof 说明。

    它与 C#中的运算符。基本上,它得到一个结构的大小(或者引用类型,通过一些优化可以搞笑),包括padding和all。所以,如果你要创建一个 T 以字节为单位 . 它也是完全可验证和可管理的代码。注意,在 Mono

    无论如何,您可以下载BSD许可库(和CIL) from BitBucket . 您还可以看到一些示例代码和更多细节 at my blog .

        5
  •  6
  •   Glenn Slayden    7 年前


    using System;
    using System.Diagnostics;
    using System.Reflection.Emit;
    using System.Runtime.InteropServices;
    


    [Serializable, StructLayout(LayoutKind.Sequential, Pack = 128)]
    public struct T
    {
        public int a;
        public byte b;
        public int c;
        public String d;
        public short e;
    };
    

    2减去托管指针:

    /// Return byte-offset between managed addresses of struct instances 'hi' and 'lo'
    public static long IL<T1,T2>.RefOffs(ref T1 hi, ref T2 lo) { ... }
    
    public static class IL<T1, T2>
    {
        public delegate long _ref_offs(ref T1 hi, ref T2 lo);
    
        public static readonly _ref_offs RefOffs;
    
        static IL()
        {
            var dm = new DynamicMethod(
                Guid.NewGuid().ToString(),
                typeof(long),
                new[] { typeof(T1).MakeByRefType(), typeof(T2).MakeByRefType() },
                typeof(Object),
                true);
    
            var il = dm.GetILGenerator();
            il.Emit(OpCodes.Ldarg_0);
            il.Emit(OpCodes.Ldarg_1);
            il.Emit(OpCodes.Sub);
            il.Emit(OpCodes.Conv_I8);
            il.Emit(OpCodes.Ret);
            RefOffs = (_ref_offs)dm.CreateDelegate(typeof(_ref_offs));
        }
    };
    

    三。显示托管内部结构布局:

    static class demonstration
    {
        /// Helper thunk that enables automatic type-inference from argument types
        static long RefOffs<T1,T2>(ref T1 hi, ref T2 lo) => IL<T1,T2>.RefOffs(ref hi, ref lo);
    
        public static void Test()
        {
            var t = default(T);
            var rgt = new T[2];
    
            Debug.Print("Marshal.Sizeof<T>: {0,2}", Marshal.SizeOf<T>());
            Debug.Print("&rgt[1] - &rgt[0]: {0,2}", RefOffs(ref rgt[1], ref rgt[0]));
    
            Debug.Print("int      &t.a      {0,2}", RefOffs(ref t.a, ref t));
            Debug.Print("byte     &t.b      {0,2}", RefOffs(ref t.b, ref t));
            Debug.Print("int      &t.c      {0,2}", RefOffs(ref t.c, ref t));
            Debug.Print("String   &t.d      {0,2}", RefOffs(ref t.d, ref t));
            Debug.Print("short    &t.e      {0,2}", RefOffs(ref t.e, ref t));
        }
    };
    

    4结果与讨论

    StructLayout(..., Pack) 可以将设置添加到 struct T 具有以下任一值: { 0, 1, 2, 4, 8, 16, 32, 64, 128 } . 默认值为 Pack 不是指定或等效于 Pack=0 将填料设置为 IntPtr.Size ( 4 8 在x64上)。

    运行上述程序的结果表明,该方法是可行的 包裹 值只影响 报告人 Marshal.SizeOf ,而不是单个 T 内存映像,假定为物理相邻实例之间的字节偏移量。测试代码通过诊断管理阵列来测量这一点 new T[2] 分配给 rgt公司

    ========= x86 ========== ========= x64 ==========

    -------- Pack=1 -------- --------包装=1--------
    Marshal.Sizeof(): 15 Marshal.Sizeof(): 19
    &rgt[1] - &rgt[0]: 16 &rgt[1] - &rgt[0]: 24

    -------- Pack=2 -------- --------包装=2--------
    Marshal.Sizeof(): 16 Marshal.Sizeof(): 20
    &rgt[1]-&rgt[0]:16 &rgt[1]-&rgt[0]:24

    --- Pack=4/0/default --- -------- Pack=4 --------
    Marshal.Sizeof():20 Marshal.Sizeof(): 24
    & &

    -------- Pack=8 -------- --- Pack=8/0/default ---
    Marshal.Sizeof():20 Marshal.Sizeof(): 32
    &rgt[1]-&rgt[0]:16 &rgt[1]-&rgt[0]:24

    -- Pack=16/32/64/128 --- --包装=16/32/64/128---
    Marshal.Sizeof():32
    & &rgt[1]-&rgt[0]:24

    如前所述,我们发现每个架构( x86个 , x64个 管理字段布局 设置。下面是实际的托管字段偏移量,同样适用于32位和64位模式,如上代码所述:

    ┌─offs─┐
    field type size x86 x64
    ===== ====== ==== === ===
    a int 4 4 8
    b byte 1 14 18
    c int 4 8 12
    d String 4/8 0 0
    e short 2 12 16

    在这个表中最重要的是要注意,(如 mentioned by Hans ValueType 实例总是被重新排序,以便所有引用类型的字段都排在前面。我们可以看到 String 领域 d 偏移量为0。

    进一步的重新排序优化了字段顺序,以便共享内部多余的填充,否则这些填充将被浪费。我们可以看到这个 byte 领域 ,它已从第二个声明的字段一直移动到最后一个。

    当然,通过对上一个表的行进行排序,我们可以揭示一个表的真正内部托管布局 T型 包含托管引用( String d )因此 不可空投 :

    ============= x86 ============ ============= x64 ============
    field type size offs end 字段类型大小结束
    ===== ====== ==== ==== === ===== ====== ==== ==== ===
    d String 4 0 … 4 d String 8 0 … 8
    a int 4 4 … 8 a int 4 8 … 12
    c int 4 8 … 12 c int 4 12 … 16
    e short 2 12 … 14 e short 2 16 … 18
    b byte 1 14 … 15 b byte 1 18 … 19

    internal padding: 1 15 … 16 internal padding: 5 19 … 24

    x86 managed total size: 16 x64 managed total size: 24

    前面我们通过计算相邻实例之间的字节偏移量差来确定单个托管结构实例的大小。考虑到这一点,上一个表的最后几行显示了 清除 T型 . 当然,请再次记住,这个内部填充是由CLR修复的,完全超出了我们的控制范围。


    为了完整起见,最后一个表显示了将要合成的填充量 在飞行中 在期间 . 注意,在某些情况下 Marshal 与内部托管大小相比,填充为负。例如,即使 T型 x64个 是24个字节,封送处理发出的结构可以是19或20个字节 Pack=1 Pack=2 分别是。

    pack size offs end 包装尺寸结束
    ============= ==== ==== === ============= ==== ==== ===
    1 0 15 … 15 1 0 19 … 19
    2 1 15 … 16 2 1 19 … 20
    4/8/16/32/64… 5 15 … 20 4/8/16/32/64… 5 19 … 24

        6
  •  4
  •   Peter Mortensen icecrime    8 年前

    .NET Core ,的 sizeof Unsafe 班级。添加对 System.Runtime.CompilerServices.Unsafe 打包并执行以下操作:

    int size = Unsafe.SizeOf<MyStruct>();
    

        7
  •  3
  •   Peter Mortensen icecrime    12 年前

    你想用 System.Runtime.InteropServices.Marshal.SizeOf() :

    struct s
    {
        public Int64 i;
    }
    
    public static void Main()
    {
        s s1;
        s1.i = 10;          
        var s = System.Runtime.InteropServices.Marshal.SizeOf(s1);
    }
    
        8
  •  2
  •   Jeff Mercado    14 年前

    System.Runtime.InteropServices.Marshal.SizeOf()