现代C ++编译器能否避免在某些情况下调用const函数两次?
例如,如果我有这个代码:
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_
会:
- 不改变任何成员,静态或全局variables状态
- 只调用
_pure_const_formula_
函数 - 也许还有一些其他的条件,我不记得
例如,调用一个随机数生成器将不符合这些要求。
编译器是否允许用第二个代码replace第一个代码,即使它需要recursion地挖掘到被调用的函数? 现代编译器能做到这一点吗?
GCC具有pure
(用作__attribute__((pure))
),用于告诉编译器冗余调用可以被消除。 它用在例如strlen
。
我不知道有任何编译器会自动执行此操作,特别是考虑到要调用的函数可能无法以源代码forms提供,并且目标文件格式不包含有关函数是否纯的元数据。
是的,一点没错。
编译器会一直这样做,等等 。
例如,如果所有的函数都返回true
,并且它的定义对于编译器在调用点是可见的,则整个函数调用可能会被忽略,导致:
someObscureFunction(true, true);
编译器具有足够信息的程序可以从相当复杂的任务链到可能的一个或两个指令“优化”。 现在,实际上对成员variables进行操作是在某种程度上将优化器推到了极限,但是如果variables是private
,给定了一个已知的初始值,并且不会被任何其他成员函数改变,我不明白为什么编译器如果想要的话,不能只是把它已知的价值内联。 编译器非常非常聪明。
人们认为编译好的程序是源代码中一行一对一的映射,但是这几乎是不对的。 C ++的全部目的是它是你的计算机在运行你的程序时将要做的事情的一个抽象 。
不,如果给出所示的代码,编译器不能保证所提出的优化将没有可观察到的差异,现代编译器将不能优化掉第二个函数调用。
一个非常简单的例子:这个类的方法可能会使用一个随机数生成器,并将结果保存在一些私有缓冲区中,稍后会有一部分代码读取。 显然,现在消除一个函数调用会导致更less的随机生成的值被放置在该缓冲区中。
换句话说,仅仅因为类方法是const
,并不意味着它在被调用时没有可观察的副作用。
不,编译器在这种情况下是不允许的。 const
只意味着你不改变方法所属的对象的状态。 但是,使用相同的input参数多次调用此方法可能会给出不同的结果。 例如,想一下产生随机结果的方法。
是的,现代C编译器可以省去多余的函数调用, 当且仅当它们能够certificate这样的优化performance为 – 如果遵循原始程序语义。 例如,这意味着如果函数没有副作用,并且其返回值仅依赖于参数,那么它们可以使用相同的参数消除对同一函数的多个调用。
现在,你特别提出了关于const
– 这对于开发人员来说是非常有用的,而不是编码人员。 const
函数暗示该方法不会修改被调用的对象, const
参数暗示参数不被修改。 然而,函数可能(在法律上1 )抛弃了this
指针或其参数的const
。 所以编译器不能依赖它。
而且,即使传递给一个函数的const
对象在该函数中实际上从未被修改过,并且const
函数也不会修改接收者对象,该方法可以很容易地依赖可变全局数据(并且可以改变这些数据)。 例如,考虑一个返回当前时间的函数,或者增加一个全局计数器的函数。
所以const
声明帮助程序员,而不是编译器2 。
但是,编译器可能能够使用其他技巧来certificate调用是多余的:
- 该函数可能与调用者位于同一个编译单元中,允许编译器检查它并确定它所依赖的内容。 这种内联的最终forms是内联:函数体可以被移入调用者,在这一点上优化器可以从以后的调用中删除多余的代码(最多包括来自这些调用的所有代码,并且可能是原始调用的所有或端口太)。
- 工具链可以使用某种types的链接时间优化,即使对于不同编译单元中的函数和调用者,也可以有效地实现上面描述的分析types。 这可以允许在生成最终可执行文件时对任何代码进行优化。
- 编译器可以允许用户用一个属性来注释一个函数,这个属性告诉编译器它可以把函数看作没有副作用。 例如,gcc提供了
pure
函数和const
函数属性,这些属性告诉gcc
函数没有副作用,并且仅依赖于它们的参数(在全局variables上,在pure
的情况下)。
1 通常,只要对象最初不是被定义为const
。
2 从某种意义上说, const
定义确实有助于编译器:它们可以将全局对象定义为const
并将其放在可执行文件的只读部分(如果存在这样的特性),并且在这些对象相等的情况下组合(例如, ,相同的string常量)。