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

Are modern C++ compilers able to avoid calling a const function twice under some conditions?

本文关键字:条件下 两次 函数 常量 调用 是否 编译器 C++ 现代      更新时间:2023-10-16

例如,如果我有以下代码:

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_会:

  • 不更改任何成员、静态或全局变量状态
  • 仅调用_pure_const_formula_函数
  • 也许还有其他一些我没有想到的情况

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

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

GCC具有函数的pure属性(用作__attribute__((pure))),该属性"告诉"编译器可以消除冗余调用。它用于例如strlen

我不知道有任何编译器会自动执行此操作,特别是考虑到要调用的函数可能无法以源形式使用,并且对象文件格式不包含关于函数是否纯的元数据。

当然

编译器总是这样做,甚至更多。

例如,如果您的所有函数都返回了true,并且编译器在调用站点上可以看到它的定义,那么整个函数调用可能会被忽略,结果只有:

someObscureFunction(true, true);

编译器有足够信息的程序可以从一个相当复杂的任务链"优化"到一两条指令。现在,实际操作成员变量在一定程度上会将优化器推向极限,但如果变量是private,给定了已知的初始值,并且没有被任何其他成员函数突变,我不明白为什么编译器不能在需要的时候直接内联其已知值。编译器非常聪明。

人们认为编译后的程序是源代码中行的一对一映射,但事实并非如此。C++的全部目的是,它是计算机运行程序时实际要做的事情的抽象

不,给定所示代码,编译器不能保证所提出的优化不会有可观察到的差异,也没有现代编译器能够优化掉第二个函数调用。

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

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

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

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

现在,您特别询问了关于const的问题——这对开发人员非常有用,而对编码人员来说则不然。const函数是一个提示方法不修改它所调用的对象,const参数是暗示参数不修改。然而,函数可以(合法地1)丢弃this指针或其参数的const性。所以编译器不能依赖它。

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

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

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

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

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

2const定义在某种意义上确实有助于编译器:它们可以将定义为const的全局对象放入可执行文件的只读部分(如果存在这样的功能),也可以在这些对象相等时组合它们(例如,相同的字符串常量)