【问题标题】:Is a C# struct ever boxed when declared as the return value of a function?当声明为函数的返回值时,C# 结构是否会被装箱?
【发布时间】:2010-09-18 18:27:30
【问题描述】:

一个简单的问题,但我还没有在 Stack Overflow 上找到明确的答案。

    struct MyStruct { int x, y, z; }

    MyStruct GetMyStruct() => new MyStruct();

    static void Main()
    {
        var x = GetMyStruct();      // can boxing/unboxing ever occur?
    }

C#结构(值类型)总是在从函数返回时复制到堆栈中,无论它有多大?我不确定的原因是对于MSIL以外的一些指令集(例如x86),返回值通常需要适合处理器寄存器,并且不直接涉及堆栈。

如果是这样,是否是调用站点在 CLR 堆栈上为(预期的)值返回类型预分配空间?

[编辑:回复摘要:] 对于原始问题的意图,答案是否定的; CLR 永远不会仅仅为了将结构体作为返回值发送而(默默地)装箱。

【问题讨论】:

  • 如果一个值类型的返回是 ever(你使用 'ever' 似乎暗示有时如果它发生,它不是无条件的)装箱,这意味着当我们分配返回值类型变量,这似乎效率低下。我对 MSIL 和 CLR 几乎一无所知,所以在这里我无法为您提供帮助。无论如何,我可以知道是什么引发了您的问题吗?
  • @blizpasta:当我意识到我错误地认为'ref'(和'out')关键字导致值类型的装箱(事实并非如此)时,我的原始问题实际上变得毫无意义。为了避免这种情况,我打算“归还”这些结构。但是,一旦我记得 'ref' 不会将值类型装箱,使用 out 参数就成为了一种选择。我的值类型的大小目前是 4+IntPtr.Size 字节,数十万个这样的结构被非常密集地操作。

标签: c# struct return-value boxing value-type


【解决方案1】:

这是 JIT 编译器的一个繁重的实现细节。一般来说,如果结构足够小并且具有简单的成员,那么它会在 CPU 寄存器中返回。如果它变得太大,则调用代码会在堆栈上保留足够的空间并将指向该空间的指针作为额外的隐藏参数传递。

它永远不会被装箱,除非方法的返回类型当然是object

Fwiw:这也是调试器无法在Autos窗口中显示函数返回值的原因。有时很痛苦。但是调试器没有从 JIT 编译器获得足够的元数据来确切地知道在哪里可以找到值。编辑:在 VS2013 中修复。

【讨论】:

    【解决方案2】:

    只要您想将结构视为object,它就会被装箱,因此如果您调用Func 并将结果分配给对象,它将被装箱。

    例如这样做

     object o = Func();
    

    将产生以下 IL

    L_0000: call valuetype TestApp.foo TestApp.Program::Func()
    L_0005: box TestApp.foo
    L_000a: stloc.0 
    

    这表明返回值被装箱了,因为我们将它分配给object类型的引用。

    如果将它分配给Foo 类型的变量,它不会被装箱,因此它会被复制并且值存储在堆栈中。

    此外,装箱在这里并不能真正帮助您,因为它需要创建一个对象来表示结构的值,并且在装箱操作期间会有效地复制这些值。

    【讨论】:

    • 调用站点必须在堆栈上分配适当数量的字节这一事实是 .NET 设计指南建议 structs 理想情况下不应大于 16 字节的原因。跨度>
    • @Richard:你有这个信息的来源吗?
    • 是的,包含16字节的推荐,但没有说是因为调用站点必须分配数据。
    • 我会试着找出我读到这篇文章的具体来源。
    • @JimMischel 根据答案,如果您想保证并确保避免 bltting 大型结构,您应该明确使用 refout 而不是 return结构体。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-06-15
    • 1970-01-01
    • 2021-12-09
    相关资源
    最近更新 更多