代码之家  ›  专栏  ›  技术社区  ›  Igby Largeman

取消装箱是否只返回指向堆上已装箱对象内的值的指针?

  •  9
  • Igby Largeman  · 技术社区  · 16 年前

    this MSDN Magazine article ,作者指出(强调我的):

    请注意,装箱总是创建一个新的 对象并复制未绑定值的 位到对象。 另一方面, 取消装箱只返回指向 装箱对象中的数据:否 内存复制发生。 然而,它是 通常情况下,您的代码将 因为 要复制的未绑定引用。

    我被我加粗的句子和后面的句子弄糊涂了。从我读过的所有其他东西,包括 this MSDN page ,我以前从未听说取消装箱只返回指向堆上值的指针。我的印象是取消装箱会导致您有一个变量,其中包含堆栈上的值的副本,正如您开始时所做的那样。毕竟,如果我的变量包含“指向堆上值的指针”,那么我就没有值类型,而是有一个指针。

    有人能解释一下这意味着什么吗?作者是《裂纹》的作者吗?(文章中至少还有一个明显的错误)。如果这是真的,那么“您的代码会导致未绑定引用所指向的数据无论如何都被复制”是什么情况呢?

    我刚刚注意到这篇文章已经将近10年了,所以也许这是在.NET生活的早期就发生了变化。

    3 回复  |  直到 16 年前
        1
  •  7
  •   Hans Passant    13 年前

    这篇文章是准确的。但是它谈到了什么 真正地 继续,而不是编译器生成的IL。毕竟.NET程序从不执行IL,它执行由JIT编译器从IL生成的机器代码。

    而unbox操作码确实会生成一个代码,该代码会产生一个指向堆中表示值类型值的位的指针。jit生成对clr中名为“jit_unbox”的小助手函数的调用。clr\src\vm\jithelpers.cpp如果您得到了sscli20源代码。函数的作用是:返回指针。

    从那里,最常见的值首先被复制到一个CPU寄存器中。然后可能会被存储在某个地方。它不必是堆栈,它可以是引用类型对象(GC堆)的成员。或静态变量(加载器堆)。或者可以在堆栈上推送(方法调用)。或者,当在表达式中使用值时,可以按原样使用CPU寄存器。

    调试时,右键单击编辑器窗口并选择“转到反汇编”以查看机器代码。

        2
  •  5
  •   Mike Nakis    15 年前

    原始文章的作者一定是指在IL级别发生的事情。存在两个未装箱操作码: unbox unbox.any .

    根据msdn, regarding unbox.any :

    当应用于 值类型,unbox.any指令 提取其中包含的值 OBJ(O型),因此 相当于unbox后面跟着ldobj。

    regarding unbox :

    […]不需要unbox来复制 对象的值类型。 通常它只计算 值类型的地址 已经在盒子里了 对象。

    所以,作者知道他在说什么。

    关于这个小事实 拆箱 在直接使用IL时,可以进行一些漂亮的优化。例如,如果有一个装箱的int需要传递给接受ref int的函数,则可以只发出一个 拆箱 操作码和对int的引用将在堆栈中准备好供函数操作。在这种情况下,函数将更改装箱对象的实际内容,这在C级别是不可能的。它避免了为临时局部变量分配空间的需要,取消了其中的int的绑定,将一个引用传递给该函数,然后创建一个新的装箱对象来重新装箱该int,丢弃旧的框。

    当然,当您在C级别工作时,您不能进行任何此类优化,因此通常会发生的情况是,编译器生成的代码几乎总是在进一步使用变量之前从装箱对象复制变量。

        3
  •  1
  •   Mitch Wheat    16 年前

    装箱是将值类型实例强制转换为引用类型实例(或 object 或者一个接口),并在堆上分配引用类型。

    根据“C 4.0简而言之”:“…拆箱 副本 对象的内容返回到一个值类型实例中”,这意味着在堆栈上。

    在您引用的文章中,作者声明:

    public static void Main() {
    
       Int32 v = 5;    // Create an unboxed value type variable
       Object o = v;   // o refers to a boxed version of v
       v = 123;        // Changes the unboxed value to 123
    
       Console.WriteLine(v + ", " + (Int32) o);    // Displays "123, 5"
    }
    

    根据这段代码,你能猜出有多少 装箱操作发生了吗?你可能是 惊讶地发现答案 是三!让我们分析一下代码 仔细地了解 继续。 首先,创建一个int32未装箱值类型(v),并将其初始化为 5。然后创建一个对象引用类型(O),它希望指向v。 但引用类型必须始终指向 到堆中的对象,所以c# 已生成方框V的正确IL代码 并存储了装箱人的地址 版本的V在O.Now 123中是未装箱的。 并将参考数据复制到 未装箱的值类型v;它没有 对V的装箱版本的影响,所以 盒装版本保留其值 5。注意,这个例子显示了O是如何被取消绑定的(它返回一个指向 O中的数据, 然后是O中的数据 内存是否复制到未装箱的值 V型

    推荐文章