Igb*_*man 9 .net boxing unboxing
我在MSDN杂志的这篇文章中,作者陈述(强调我的):
请注意,装箱始终会创建一个新对象,并将未装箱的值的位复制到该对象.另一方面,取消装箱只返回指向盒装对象内数据的指针:不会发生内存复制.但是,通常情况下,您的代码将导致未装箱引用指向的数据被复制.
我对我加粗的句子和随后的句子感到困惑.从我读过的其他内容,包括这个MSDN页面,我以前从未听说过取消装箱只返回指向堆上值的指针.我的印象是,拆箱会导致你有一个包含堆栈值的副本的变量,就像你开始一样.毕竟,如果我的变量包含"指向堆上的值的指针",那么我没有值类型,我有一个指针.
有人可以解释这意味着什么吗?作者是破解?(文章中至少还有一个明显的错误).如果这是真的,那么"您的代码将导致未装箱的引用指向的数据无论如何都被复制"的情况是什么?
我刚刚注意到这篇文章已经有近10年的历史了,所以也许这在.Net的生命中很早就发生了变化.
这篇文章很准确.然而,它讨论了真正发生的事情,而不是编译器生成的IL的样子.毕竟,.NET程序从不执行IL,它执行由JIT编译器从IL生成的机器代码.
并且unbox操作码确实生成了代码,该代码生成指向堆上表示值类型值的位的指针.JIT生成对CLR中名为"JIT_Unbox"的小辅助函数的调用.clr\src\vm\jithelpers.cpp如果你有SSCLI20源代码.Object :: GetData()函数返回指针.
从那里,最常见的值首先被复制到CPU寄存器中.然后可以存储在某处.它不必是堆栈,它可以是引用类型对象(gc堆)的成员.或者是静态变量(加载器堆).或者它可以被推入堆栈(方法调用).或者,当在表达式中使用该值时,可以按原样使用CPU寄存器.
在调试时,右键单击编辑器窗口并选择"转到反汇编"以查看机器代码.
原始文章的作者必须指的是在IL级别上发生的事情.存在两个拆箱操作码:unbox和unbox.any.
根据MSDN,关于unbox.any:
当应用于值类型的盒装形式时,unbox.any指令将提取obj(类型O)中包含的值,因此等效于unbox后跟ldobj.
而有关unbox:
[...] unbox不需要从对象复制值类型.通常,它只是计算已装箱对象内部已存在的值类型的地址.
所以,作者知道他在说什么.
这个小事实unbox使得在直接使用IL时可以进行某些漂亮的优化.例如,如果你有一个盒装的int,你需要传递给一个接受ref int的函数,你可以只发出一个unbox操作码,并且int的引用将在堆栈中准备好供函数操作.在这种情况下,该函数将改变装箱对象的实际内容,这在C#级别是非常不可能的.它可以节省您为临时局部变量分配空间的需要,在那里取消int,将ref传递给函数的int,然后创建一个新的装箱对象来重新封装int,丢弃旧盒子.
当然,当你在C#级别工作时,你不能做任何这样的优化,所以通常会发生的事情是编译器生成的代码几乎总是在进一步使用之前从盒装对象复制变量它的.