【问题标题】:Calling a non-void function without using its return value. What actually happens?调用非 void 函数而不使用其返回值。实际发生了什么?
【发布时间】:2011-04-29 05:48:05
【问题描述】:

所以,我发现了一个类似的问题here,但答案更多的是关于风格以及你是否能够做到。

我的问题是,当您调用一个返回对象的非 void 函数但您从未分配或使用所述返回的对象时,实际会发生什么?所以,少说你是否可以,因为我绝对知道你可以并且理解上面链接的另一个问题......编译器/运行时环境是做什么的?

这不是一个特定于语言的问题,但如果您回答,请说明您所指的语言,因为行为会有所不同。

【问题讨论】:

    标签: c# java c++ return-value void


    【解决方案1】:

    我相信对于 C# 和 Java,结果最终都会在堆栈上,然后编译器会强制弹出指令忽略它。 Eric Lippert 在"The void is invariant" 上的博文提供了更多相关信息。

    例如,考虑以下 C# 代码:

    using System;
    
    public class Test 
    {
        static int Foo() { return 10; }
        
        public static void Main()
        {
            Foo();
            Foo();
        }
    }
    

    Main 方法生成的IL(由MS C# 4 编译器)是:

    .method public hidebysig static void Main() cil managed
    {
        .entrypoint
        .maxstack 8
        L_0000: call int32 Test::Foo()
        L_0005: pop 
        L_0006: call int32 Test::Foo()
        L_000b: pop 
        L_000c: ret 
    }
    

    注意对 pop 的调用 - 如果您将 Foo 设为 void 方法,这些调用就会消失。

    【讨论】:

    • 也许 C# 不是那么聪明,但在这个例子中,编译器不会内联方法吗?此外,为这样的调用弹出堆栈有多昂贵?如果将其循环 250k 次,您可能会看到差异,但对于您的平均方法调用,我不会因为额外的一两次弹出而失眠,除非您从未在代码中的任何地方使用过返回值。
    • @KeithS 我也想知道它有多贵,但在我的特殊情况下,这不是问题。我有一个方法可以在幕后对多个地图进行一些缓存,但也返回请求的列表,另一种方法依赖于缓存并从第一个方法生成的一个缓存地图返回不同的列表。我想我可以重构地图生成以使其更清晰,并在必要时完全避免这种额外的弹出。
    • @Keith:C# 编译器不会内联它,但 JIT 可能会这样做。 Java 中的同上。托管语言中的大多数优化都是由 VM 而不是语言编译器完成的。
    【解决方案2】:

    编译器做了什么?

    编译器生成一条弹出指令,将结果从虚拟堆栈中丢弃。

    运行时环境有什么作用?

    它通常将代码 jit 为将返回值传回寄存器而不是堆栈位置的代码。 (通常是 x86 架构上的 EAX。)

    jitter 知道该值将未被使用,因此它可能会生成清除寄存器的代码。或者也许它只是让它在寄存器中徘徊一段时间。

    您关心哪个运行时环境?它们有很多,而且它们都有不同的抖动。

    【讨论】:

      【解决方案3】:

      这在一定程度上取决于所使用的调用约定。对于小型/简单类型,返回通常发生在寄存器中。在这种情况下,该函数会将值写入寄存器,但没有其他注意事项,并且下次需要该寄存器时(通常会很快发生),它将被其他内容覆盖。

      对于较大的类型,编译器通常会分配一个结构来保存返回值。但是,分配的确切位置/方式会因编译器而异——在某些情况下,它将是一个静态结构,并且下次调用返回相同类型的函数时,内容将被忽略并覆盖。在其他情况下它会在堆栈上,即使你没有使用它,它仍然需要在函数调用之前分配,然后释放

      【讨论】:

      • 你指的是什么语言?
      • @DomenicDatti:我试图让答案足够笼统,以合理地适用于所有三个问题。在虚拟机上,堆栈将被虚拟化,并且在 JIT 编译器运行之前它不能使用寄存器,但 Java 和 .NET 通常都使用 JIT。不过,这些都对结果没有任何重大影响。
      【解决方案4】:

      对于 C++,编译器通常会优化变量的返回,将其转换为 void 函数,如果这样可以进一步优化,编译器可能会优化整个函数调用或仅与返回值。对于 Java 和 C#,我不太了解。

      【讨论】:

        【解决方案5】:

        在 .NET 中,如果返回的对象是引用类型,并且应用程序没有对该对象的其他引用,那么在垃圾收集器决定收集它之前,您仍然会有一个对象在内存中浮动。

        如果返回的对象碰巧持有资源,这可能会很糟糕。如果它实现了IDisposable 接口,那么你的代码应该调用它的 Dispose 方法,但在这种情况下永远不会调用 Dispose 方法。

        编辑:更正了一个错字。

        【讨论】:

          猜你喜欢
          • 2017-10-10
          • 2013-06-17
          • 1970-01-01
          • 1970-01-01
          • 2016-09-17
          • 1970-01-01
          • 1970-01-01
          • 2023-04-05
          • 1970-01-01
          相关资源
          最近更新 更多