现代C ++编译器是否能够在某些条件下避免两次调用const函数?

时间:2017-02-14 13:33:05

标签: c++ optimization compiler-construction call

例如,如果我有这段代码:

var updatedNormalizedValue: Float

@IBAction func valuechanged(_ slider: UISlider) {
    let min = slider.minimumValue
    let max = slider.maximumValue
    let interval = max - min
    updatedNormalizedValue = (slider.value - min) / interval
}

让我们考虑可能等效代码:

class SomeDataProcessor
{
public:
    bool calc(const SomeData & d1, const SomeData & d2) const;
private:
    //Some non-mutable, non-static member variables
}

SomeDataProcessor sdp;
SomeData data1;
SomeData data2;

someObscureFunction(sdp.calc(data1, data2),
                    sdp.calc(data1, data2));

为了使其有效,bool b = sdp.calc(data1, data2); someObscureFunction(b,b); 函数应满足某些要求,例如我调用属性calc()

_pure_const_formula_会:

  • 不更改任何成员,静态或全局变量状态
  • 仅调用_pure_const_formula_个函数
  • 也许其他一些我不记得的情况

例如,调用随机数生成器不符合这些要求。

编译器是否允许用第二个代码替换第一个代码,即使它需要递归地挖掘到被调用的函数中?现代编译器能够做到这一点吗?

5 个答案:

答案 0 :(得分:46)

GCC的pure attribute(用作__attribute__((pure)))用于告诉编译器可以消除冗余调用的函数。它用于例如在strlen

我不知道有任何编译器会自动执行此操作,特别是考虑到要调用的函数可能无法以源格式提供,并且目标文件格式不包含有关函数是纯函数还是不。

答案 1 :(得分:22)

是的,绝对。

Compilers do this all the time, and more

例如,如果您的所有函数都返回true,并且其定义对于调用点处的编译器是可见的,那么整个函数调用可能会被省略,从而导致:

someObscureFunction(true, true);

编译器具有足够信息的程序可以从非常复杂的任务链“优化”到可能一个或两个指令。现在,实际操作成员变量正在将优化器推到某种程度,但如果变量是private,则给出一个已知的初始值,并且不会被任何其他成员函数变异,我不会看看为什么编译器不能只是想要内联它的已知值。编译器非常非常聪明。

人们认为编译的程序是源代码中行的一对一映射,但这几乎不是真的。 C ++的全部目的是它是计算机在运行程序时实际要做的抽象

答案 2 :(得分:10)

不,鉴于显示的代码,编译器无法保证建议的优化没有可观察到的差异,并且现代编译器也无法优化第二个函数调用。

一个非常简单的例子:这个类方法可能使用随机数生成器,并将结果保存在某个私有缓冲区中,稍后会读取其他部分代码。显然,消除函数调用现在导致在该缓冲区中放置的随机生成的值更少。

换句话说,仅仅因为类方法const并不意味着它在被调用时没有可观察到的副作用。

答案 3 :(得分:4)

不,在这种情况下,编译器不允许这样做。 const仅表示您不更改方法所属对象的状态。但是,使用相同的输入参数多次调用此方法可能会产生不同的结果。例如,考虑一种产生随机结果的方法。

答案 4 :(得分:1)

是的,现代C编译器可以忽略冗余函数调用当且仅当他们可以证明这样的优化行为 as 遵循原始程序语义。例如,这意味着它们可以消除对具有相同参数的同一函数的多次调用,如果该函数没有副作用并且其返回值仅取决于参数。

现在,您具体询问了const - 这对开发人员而非编码人员非常有用。 const函数是一个提示,该方法不会修改它所调用的对象,而const参数是提示那些参数没有修改。但是,该函数可能(合法地 1 )抛弃const指针或其参数的this - ness。所以编译器不能依赖它。

此外,即使传递给函数的const对象确实从未在该函数中被修改过,并且const函数从未修改过接收器对象,该方法也很容易依赖于可变的全局数据(并且可能改变这样的数据)。例如,考虑一个返回当前时间的函数,或者递增全局计数器的函数。

因此const声明有助于程序员,而不是编译器 2

但是,编译器可能能够使用其他技巧来证明调用是多余的:

  • 该函数可以与调用者位于同一个编译单元中,允许编译器检查它并确切地确定它所依赖的内容。最终的形式是内联:函数体可以移动到调用者,此时优化器可以从以后的调用中删除冗余代码(最多包括来自那些调用的所有代码,也可能是原始调用的全部或端口)太)。
  • 工具链可以使用某种类型的链接时间优化,这有效地允许上面描述的分析类型,甚至是不同编译单元中的函数和调用者。这可以允许在生成最终可执行文件时对任何代码进行此优化。
  • 编译器可以允许用户使用属性来注释函数,该属性通知编译器它可以将该函数视为没有副作用。例如,gcc提供pureconst函数属性,通知gcc函数没有副作用,并且仅依赖于它们的参数(以及全局变量,如果是pure)。

1 通常,只要对象最初未定义为const

2 从某种意义上说,const 定义可以帮助编译器:它们可以将定义为const的全局对象放入只读部分可执行文件(如果存在这样的特征)并且当它们相等时也组合这些对象(例如,相同的字符串常量)。