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

gal*_*tte 37 c++ compiler-construction optimization call

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

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));
Run Code Online (Sandbox Code Playgroud)

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

bool b = sdp.calc(data1, data2);
someObscureFunction(b,b);
Run Code Online (Sandbox Code Playgroud)

为了使其有效,该calc()函数应满足一些要求,对于该示例,我调用该属性_pure_const_formula_

A _pure_const_formula_会:

  • 不更改任何成员,静态或全局变量状态
  • 仅限通话_pure_const_formula_功能
  • 也许其他一些我没有想到的条件

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

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

Tam*_*ola 46

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

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

  • 请记住,即使使用`pure`属性,如果gcc可以通过静态分析确定它们确实使用相同的参数调用,那么它只会忽略冗余调用.如果编译器无法执行此操作,您仍然可以通过memoization消除冗余调用,但C/C++编译器不会自动为您执行此操作. (9认同)
  • 在大多数语言中,纯函数确定是一种非常常见的编译器优化.在C中很难(主要是由于指针),但即使GCC也会尝试使用编译器标志`-Wsuggest-attribute = pure`(当它认为函数可以标记为`pure`时会发出警告). (7认同)
  • @nwp你正在修改字符串,所以它不是同一个参数.指针是相同的,但它指向的数据已被修改. (2认同)

Lig*_*ica 22

是的,一点没错.

编译器会一直这样做,甚至更多.

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

someObscureFunction(true, true);
Run Code Online (Sandbox Code Playgroud)

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

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

  • @cmaster除了Lightness的其他观察之外,链接时优化还可以使函数内联甚至跨越对象边界. (10认同)
  • @cmaster:再次阅读答案._"如果你的所有函数都返回true,**并且它的定义对于调用点**的编译器是可见的,整个函数调用可能会被省略"_我不是"假设"任何东西! (5认同)
  • 我已经看到clang做了一些优化,人们从来没有想过编译器能够做到(有时由于人们依赖于这种信念而导致可怕的结果)和链接时优化. (5认同)
  • @cmaster MSVC,Clang和GCC已经支持LTO多年了,所以像"定义内联"这样的概念正在从优化的角度迅速变得过时. (4认同)
  • @KonradRudolph可以说LTO意味着"编译器"会在调用点的链接时检查定义. (3认同)
  • @Leushenko:另外,如果定义在同一个TU中可见,"定义内联"总是无关紧要的. (2认同)

Sam*_*hik 10

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

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

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

  • OP已经表示会有限制.你重复了一遍.如果你想把问题中的代码作为福音,字面上,逐字逐句,那么你必须抛弃一半的问题散文并报告该程序甚至不会编译,更不用说链接,更不用说优化了. (5认同)
  • `no modern compiler`你是否故意使用_compiler_的限制性定义来不包括链接时优化?因为**所有**主要的现代编译器工具链都支持LTO,这意味着可以考虑在每个呼叫站点优化函数的定义.(当然,如果函数在标题中声明并且它们可以看到它没有其他副作用,那么即使那些不能优化它也可以.) (5认同)
  • 有现代编译器(例如clang)经常可以告诉所提议的优化没有可观察到的差异,事实上确实优化了第二个函数调用.当然,任何在非法的情况下进行此优化的编译器都会被破坏,但是编译器无法在合法的地方进行优化.(并且有很多人认为编译器永远不够智能,无法做出特别的优化,然后在它们出现时会被严重烧毁.) (2认同)