sizeof(T) 和 Unsafe.SizeOf<T>() 有啥区别?

Posted

技术标签:

【中文标题】sizeof(T) 和 Unsafe.SizeOf<T>() 有啥区别?【英文标题】:What's the difference between sizeof(T) and Unsafe.SizeOf<T>()?sizeof(T) 和 Unsafe.SizeOf<T>() 有什么区别? 【发布时间】:2017-12-21 10:09:17 【问题描述】:

首先,在实际问题之前的一个小免责声明:

我知道关于 sizeof 运算符和 Marshal.SizeOf&lt;T&gt; 方法之间的区别有很多封闭/重复的问题,我确实了解两者之间的区别。这里我说的是新的Unsafe类中的SizeOf&lt;T&gt;方法

因此,我不确定我是否了解这两个操作之间的实际区别,以及在特别是在结构/类上使用该方法时是否存在特定区别。

sizeof 运算符采用 类型名称 并返回分配时它应该占用的 托管字节数(即 Int32 将返回4,例如)。

另一方面,Unsafe.SizeOf&lt;T&gt; 方法是在 IL 中实现的,就像 Unsafe 类中的所有其他方法一样,看看下面的代码就是它的作用:

.method public hidebysig static int32 SizeOf<T>() cil managed aggressiveinlining

    .custom instance void System.Runtime.Versioning.NonVersionableAttribute::.ctor() = ( 01 00 00 00 )
    .maxstack 1
    sizeof !!T
    ret

现在,如果我没记错的话,代码只是调用sizeof !!T,这与sizeof(T) 相同(使用类型名称T 调用sizeof 运算符),所以两者不会它们中的哪一个是完全等价的?

另外,我看到该方法还在第一行分配了一个无用的对象(NonVersionableAttribute),那么这不会导致少量内存也被堆分配吗?

我的问题是:

可以肯定地说这两种方法是完全等价的,因此最好使用经典的sizeof 运算符,因为这样也可以避免在SizeOf&lt;T&gt; 方法中分配该属性?这个 SizeOf&lt;T&gt; 方法是否添加到 Unsafe 类中只是为了方便?

【问题讨论】:

.custom 语句只是告诉您存在属于此方法的自定义属性。由于自定义属性只是元数据,它们不参与常规方法调用。 只是澄清一下,这是我不知道的 BCL 类,还是 third party extension?你说得对,它什么都做不了,也许这个想法是为将来的一些跨平台魔法提供一个“钩子”? @Groo 这是一个添加到 CoreFX 的新类,我相信 .NET Core 2.0 (不完全确定,但无论如何它都存在于 .NET Core 2.0 中)。见这里:docs.microsoft.com/it-it/dotnet/api/… 这是实现 System.Memory 的必要技巧。 coreclr 跟踪器问题is here。一个纯粹的实现细节,它返回的值取决于运行时使用的抖动。 C# 编译器仍然拒绝任何获取非 blittable 类型大小的尝试。 【参考方案1】:

虽然此方法确实只使用sizeof IL 指令 - 与常规的sizeof 运算符有所不同,因为此运算符不能应用于任意类型:

用于获取非托管类型的大小(以字节为单位)。非托管 类型包括表中列出的内置类型 如下,还有以下:

枚举类型

指针类型

不包含任何内容的用户定义结构 引用类型的字段或属性

如果您尝试编写 Unsafe.SizeOf 的模拟 - 它不会起作用:

public static int SizeOf<T>()

    // nope, will not compile
    return sizeof(T);

所以Unsafe.SizeOf 解除了sizeof 运算符的限制,并允许您使用任意类型的IL sizeof 指令(包括它将返回引用大小的引用类型)。

至于你在 IL 中看到的属性构造——这并不意味着每次调用都会实例化属性——这只是用于将属性与各种成员相关联的 IL 语法(本例中的方法)。

例子:

public struct Test 
    public int Int1;


static void Main() 
    // works
    var s1 = Unsafe.SizeOf<Test>();
    // doesn't work, need to mark method with "unsafe"
    var s2 = sizeof(Test);            

另一个例子:

public struct Test 
    public int Int1;
    public string String1;



static unsafe void Main() 
    // works, return 16 in 64bit process - 4 for int, 4 for padding, because
    // alignment of the type is the size of its largest element, which is 8
    // and 8 for string
    var s1 = Unsafe.SizeOf<Test>();
    // doesn't work even with unsafe, 
    // cannot take size of variable of managed type "Test"
    // because Test contains field of reference type (string)
    var s2 = sizeof(Test);                        
 

【讨论】:

完美,谢谢!虽然我们需要这个方法来做到这一点有点奇怪,但我想知道为什么他们选择不更新 C# 编译器让 sizeof 运算符在没有额外的 IL 方法的情况下做同样的事情。另外,等一下,int(因为它是Int32 结构)不应该总是 4 字节长,即使是 64 位进程? Test 的大小不应该是 12 字节吗? @Sergio0694 是的,当然,int 是 4 个字节,另外 4 个字节是填充,修复了这个问题。至于为什么不更改 sizeof 运算符 - 我当然不知道,但我想这需要更改编译器,Unsafe.SizeOf 不需要任何更改。另外,让sizeof 那样工作首先是有原因的,所以我想你需要真正知道在使用Unsafe.SizeOf 时你在做什么(毕竟这是不安全的)。 有道理,感谢您的额外解释!完全忘记了字段偏移对齐,自从我上次用 C 编码以来已经有一段时间了,使用托管语言时你往往会忘记这些微小的细节啊哈哈哈 @Sergio0694 它被称为 Unsafe 是有原因的。最好使用安全的语言和可能不安全的 API,而不是单独使用不安全的语言。

以上是关于sizeof(T) 和 Unsafe.SizeOf<T>() 有啥区别?的主要内容,如果未能解决你的问题,请参考以下文章

底层编程(unsafe包)

Golang内存对齐

golang有没有必要传递map指针

golang 字节对齐

go中浮点型用法总结

使用unsafe.Pointer将结构体转为[]byte