【问题标题】:Will the c++ compiler optimize away unused return value?c++ 编译器会优化掉未使用的返回值吗?
【发布时间】:2010-09-21 08:08:44
【问题描述】:

如果我有一个返回对象的函数,但调用者从不使用这个返回值,编译器会优化掉副本吗? (可能总是/有时/从不回答。)

基本示例:

ReturnValue MyClass::FunctionThatAltersMembersAndNeverFails()
{
    //Do stuff to members of MyClass that never fails
    return successfulResultObject;
}

void MyClass::DoWork()
{
    // Do some stuff
    FunctionThatAltersMembersAndNeverFails();
    // Do more stuff
}

在这种情况下,ReturnValue 对象会被复制吗?它甚至可以构建吗? (我知道这可能取决于编译器,但让我们将讨论范围缩小到流行的现代编译器。)

编辑:让我们稍微简化一下,因为在一般情况下似乎没有达成共识。如果ReturnValue 是一个int,我们返回0 而不是successfulResultObject 怎么办?

【问题讨论】:

    标签: c++ visual-c++ gcc compiler-construction return


    【解决方案1】:

    如果 ReturnValue 类具有重要的复制构造函数,编译器不得消除对复制构造函数的调用 - 它是由调用它的语言强制要求的。

    如果复制构造函数是内联的,编译器可能能够内联调用,这反过来可能会导致删除其大部分代码(也取决于 FunctionThatAltersMembersAndNeverFails 是否内联)。

    【讨论】:

    • 不是这样。在临时对象的特定情况下,编译器具有直接在其目标中构造对象而不是复制它的明确权限(参见 ISO 14882 §12.2)。如果中间对象有名字,那你就对了。
    • 在给定的例子中,它怎么能不调用复制构造函数(假设successfulResultObject的类型已经是ReturnValue)?在这种特定情况下,“直接复制”仍然涉及复制构造函数。
    • “编译器不能消除对复制构造函数的调用”——这个信息似乎是错误的,否则NRVO 是不可能的,不是吗?
    【解决方案2】:

    如果优化级别导致他们内联代码,他们很可能会这样做。如果不是,他们将不得不为相同的代码生成两种不同的翻译才能使其正常工作,这可能会引发很多极端情况问题。

    【讨论】:

      【解决方案3】:

      链接器可以处理这类事情,即使原始调用者和被调用者位于不同的编译单元中。

      如果您有充分的理由担心专用于方法调用的 CPU 负载(过早的优化是万恶之源),您可能会考虑许多可用的内联选项,包括(喘气!)宏.

      您真的需要在这个级别进行优化吗?

      【讨论】:

      • 链接器不能做任何这样的优化。被调用的函数不知道结果是否被使用,因此必须生成它。只有编译器有足够的知识才能接近优化它,而且只有在所有内容都内联时才会发生这种情况。
      • 我认为他可能在谈论像微软这样的链接器,我相信它需要所有对象的中间表示并执行另一个编译步骤以进行模块间优化。
      • @ZanLynx 不再只是 MS,GCC 具有非常完善的链接时间优化,并且打开它意味着编译器可见的任何内容现在对链接器可见(以中间格式流式传输)并且可以跨翻译单元边界内联或以其他方式优化,通常会产生显着的结果。我认为这同样适用于 LLVM/Clang 和任何其他跟上竞争的现代编译器
      【解决方案4】:

      如果返回值是一个 int 并且您返回 0(如已编辑的问题中所示),那么这个 可能 会被优化掉。

      您必须查看底层程序集。如果函数未内联,则底层程序集将执行 mov eax, 0(或 xor eax, eax)将 eax(通常用于整数返回值)设置为 0。如果函数内联,这肯定会得到优化了。

      但是,如果您担心返回大于 32 位的对象时会发生什么,那么这个场景就不是很有用了。您需要参考 unedit 问题的答案,这描绘了一幅相当不错的图景:如果所有内容都是内联的,那么大部分内容都会被优化掉。如果它不是内联的,那么即使它们没有真正做任何事情,也必须调用函数,这包括对象的构造函数(因为编译器不知道构造函数是否修改了全局变量或做了其他奇怪的事情) .

      【讨论】:

        【解决方案5】:

        如果大多数编译器位于不同的编译对象(即不同的文件)中,我怀疑它们是否可以做到这一点。也许如果他们都在同一个文件中,他们可以。

        【讨论】:

        • 同意。启用“整个程序优化”也可以实现它,前提是从未在任何调用中使用返回值。
        • @Drew,是“整个程序优化”一个 vc++ 的东西,因为我无法想象它在 Linux/Unix 环境中是可能的。
        • 有关努力的传闻,例如:airs.com/blog/archives/100
        • @Paul,这是可能的,但你基本上告诉 GCC 编译 all 你的源文件并给它 -fwhole-program 标志。它可以轻松使用 1.5 GB RAM 及更多。
        【解决方案6】:

        窥视孔优化器很有可能会捕捉到这一点。许多(大多数?)编译器都实现了一个,所以答案可能是“是”。

        正如其他人所指出的,这在 AST 重写级别上并不是一个微不足道的问题。


        Peephole 优化器在相当于汇编语言的级别上处理代码表示(但在生成实际机器代码之前)。有机会注意到将返回值加载到寄存器中,然后在没有中间读取的情况下进行覆盖,然后移除加载。这是根据具体情况进行的。

        【讨论】:

          【解决方案7】:

          刚刚在编译器资源管理器上尝试了这个示例,在 -O3 处,不使用返回值时不会生成mov

          https://gcc.godbolt.org/z/v5WGPr

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2012-04-21
            相关资源
            最近更新 更多