什么是双重评估,为什么要避免?

Ank*_*thD 48 c++ macros preprocessor-directive

我正在使用像C++这样的C++阅读它

#define max(a,b) (a > b ? a : b)
Run Code Online (Sandbox Code Playgroud)

可以导致"双重评估".有人能给我一个例子,说明何时进行双重评估以及为什么它会变坏?

PS:令人惊讶的是,除了在Clojure中的一个例子(我无法理解)之外,我在google搜索时找不到任何详细的解释.

Whi*_*TiM 70

想象一下你写的这个:

#define Max(a,b) (a < b ? b : a)

int x(){ turnLeft();   return 0; }
int y(){ turnRight();  return 1; }
Run Code Online (Sandbox Code Playgroud)

然后这样叫:

auto var = Max(x(), y());
Run Code Online (Sandbox Code Playgroud)

你知道turnRight()会被执行两次吗?那个宏,Max将扩展到:

auto var = (x() < y() ? y() : x());
Run Code Online (Sandbox Code Playgroud)

评估条件后x() < y(),该程序然后采取之间所需的分支y() : x():在我们的例子中true,它调用y()第二次.看到Live On Coliru.

简单地说,将表达式作为参数传递给类似函数的宏,Max可能会对该表达式进行两次计算,因为表达式将在宏的参数中重复使用宏参数,在宏的定义中使用.请记住,宏由预处理器处理.


所以,底线是,不要使用宏来定义一个函数(在这种情况下实际上是一个表达式),因为你希望它是通用的,而它可以使用函数模板有效地完成

PS:C++有一个std::max模板功能.

  • @PatrickM'Bongo:这两点看起来都毫无意义.`std :: max <double>(12.f,13.)`是消除歧义的常用方法.并且没有函数可以延长其参数的生命周期,这不是C++的工作方式.这并不重要:无论如何,引用都不会悬挂到完整表达式的结尾.只有当你用它来初始化另一个引用时才重要,此时你显然应该对你的引用负责. (2认同)
  • @MSalters不,在理智的情况下,你返回为`common_type_t <Ts ...>`,因为这是免费的`?:`. (2认同)

Fra*_*nck 24

ab在宏定义中出现两次.因此,如果您将其与具有副作用的参数一起使用,则副作用将被执行两次.

max(++i, 4);
Run Code Online (Sandbox Code Playgroud)

如果i = 4在通话之前将返回6 .由于它不是预期的行为,您应该更喜欢内联函数来替换像这样的宏max.

  • 另外,这不是**不是一个未操作的行为`((++ i)<(4)?(++ i)(4))`,因为有一个[序列点](https:// en. wikipedia.org/wiki/Sequence_point)在评估第一个'++ i`之后.但是,在同一行上对同一个变量使用几个增量通常不是一个好主意. (3认同)

sel*_*bie 20

请考虑以下表达式:

 x = max(Foo(), Bar());
Run Code Online (Sandbox Code Playgroud)

在哪里FooBar这样:

int Foo()
{
    // do some complicated code that takes a long time
    return result;
}

int Bar()
{
   global_var++;
   return global_var;
}
Run Code Online (Sandbox Code Playgroud)

然后在原始max表达式中扩展如下:

 Foo() > Bar() ? Foo() : Bar();
Run Code Online (Sandbox Code Playgroud)

无论哪种情况,Foo或Bar都要执行两次.从而花费超过必要的时间或改变程序状态超过预期的次数.在我的简单Bar示例中,它不会一致地返回相同的值.


kfs*_*one 8

C和C++中的宏语言由"预处理"阶段的专用解析器处理; 翻译令牌,然后将输出馈送到解析器本身的输入流中.C或C++解析器本身无法识别#define#include令牌.

这很重要,因为这意味着当一个宏被称为"扩展"时,它意味着字面意思.特定

#define MAX(A, B) (A > B ? A : B)

int i = 1, j = 2;
MAX(i, j);
Run Code Online (Sandbox Code Playgroud)

C++解析器看到的是什么

(i > j ? i : j);
Run Code Online (Sandbox Code Playgroud)

但是,如果我们使用更复杂的宏,则会发生相同的扩展:

MAX(i++, ++j);
Run Code Online (Sandbox Code Playgroud)

扩大到

(i++ > ++j ? i++ : ++j);
Run Code Online (Sandbox Code Playgroud)

如果我们传递了一个函数调用的东西:

MAX(f(), g());
Run Code Online (Sandbox Code Playgroud)

这将扩展到

(f() > g() ? f() : g());
Run Code Online (Sandbox Code Playgroud)

如果编译器/优化器可以证明f()没有副作用,那么它会将其视为

auto fret = f();
auto gret = g();
(fret > gret) ? fret : gret;
Run Code Online (Sandbox Code Playgroud)

如果不能,则必须调用f()和g()两次,例如:

#include <iostream>

int f() { std::cout << "f()\n"; return 1; }
int g() { std::cout << "g()\n"; return 2; }

#define MAX(A, B) (A > B ? A : B)

int main() {
    MAX(f(), g());
}
Run Code Online (Sandbox Code Playgroud)

现场演示:http://ideone.com/3JBAmF

类似地,如果我们调用一个extern函数,优化器可能无法避免两次调用该函数.