G++:移动到另一个翻译单元会中断 "const optimization"?

G++: Moving to another translation unit breaks "const optimization"?

我正在制作演示各种 C++ 优化的演示文稿,并坚持使用 const 允许它们的示例。

考虑以下代码:

object.h

class Object {
    int i1;
    int i2;
public:
    Object(int i1_, int i2_) : i1(i1_), i2(i2_) {}

    int getI1() const { return i1; }
    int getI2() const { return i2; }

    std::pair<int, int> calculate() const {
        return std::pair<int, int>(i1 + i2, i1 * i2);
    }
};

constopt.cpp

#include <iostream>
#include "object.h"

int main() {
    Object o(10, 20);

    std::cout << o.getI1() << " + " << o.getI2()  << " = " 
                << o.calculate().first << std::endl
            << o.getI1() << " * " << o.getI2()  << " = " 
                << o.calculate().second << std::endl;

    return 0;
}

内联calculate()时,一切正常,G++直接将常量(10和20)传递给operator <<缓存getI1()getI2()调用:

mov    [=13=]xa,%esi
mov    [=13=]x601080,%edi
callq  0x400740 <_ZNSolsEi@plt>

但是当我将 calculate() 移动到一个单独的翻译单元时,它会强制提取 i1i2 两次(在 o.calculate().first 之前和之后):

mov    (%rsp),%esi
mov    0x4(%rsp),%r14d
mov    [=14=]x601080,%edi
callq  0x400740 <_ZNSolsEi@plt>

我看不出有什么区别,因为 getI1() 不依赖于 calculate() 可以产生的任何副作用,并且 Object 即使在 [=16] 时也是常量=] 在单独的翻译单元中。

是不是G++不够聪明,或者说它没有资格在这种情况下进行优化?我的猜测是它可以缓存 getI1()getI2() 调用来自那个答案:How does const after a function optimize the program?

我使用 gcc 4.8.1 版。 -Os 和 -O2.

我都试过了

在这种情况下,GCC 优化器似乎没有使用 const。相反,它会自己挖掘(不能在不同的翻译单元中执行),并搜索 pure and const functions。函数可以用 __attribute__((const)) 手动标记。消除额外 getI*() 调用的阶段称为 FRE(完全冗余消除)。

好吧,要准确回答您的问题,必须了解编译器的实现细节。我不知道,所以以下内容纯属推测。

Object::i1Object::i2 未声明为常量。因此,编译器在没有看到 Object::calculate 的定义的情况下,不得不假定它们可能会发生变化。

Object::calculate 本身是 const 本身并不排除它对 Object::i1Object::i2 进行更改。考虑以下稍作修改的示例:

class Object {
    int i1;
    int i2;
    int* p1;
public:
    Object(int i1_, int i2_) : i1(i1_), i2(i2_), p1(&i1) {}

    int getI1() const { return i1; }
    int getI2() const { return i2; }

    std::pair<int, int> calculate() const;
};

std::pair<int, int> Object::calculate() const {
    (*p1)++;
    return std::pair<int, int>(i1 + i2, i1 * i2);
}

此外,o 一开始就没有被声明为 const,因此 Object::calculate 有权做出像 const_cast 那样粗鲁的事情并逍遥法外!

because getI1() doesn't rely on any side effects that can be created by calculate()

可能会,但 C++ 标准未指定是否会。

即使您在调用 calculate() 之前将 getI1()getI2() 写入流,在 C++ 中,函数参数的计算顺序是未指定的。这意味着在对 std::cout 进行任何实际写入之前,允许编译器以任何顺序对 calculate()getI1() 以及 getI2() 进行所有调用。如果它决定在调用 getI1()getI2() 之前对 calculate() 进行一次或两次调用,则它不能假定 i1 和 [=22= 的值]没变。

如果将表达式拆分,那么编译器应该能够看到 i1i2 在调用 calculate():

之前不能更改
// this statement uses i1 and i2 before they can possibly be changed
std::cout << o.getI1() << " + " << o.getI2()  << " = ";

// this statement might mutate i1 and i2
std::cout << o.calculate().first << std::endl
        << o.getI1() << " * " << o.getI2()  << " = " 
            << o.calculate().second << std::endl;

and Object is intended to be const even when calculate() is in separate translation unit.

但你实际上并没有声明 oconst

调用 const 成员函数并不能保证成员 不能 被改变,它更像是调用者和函数之间的非正式契约,而不是保证.