什么是双重求值,为什么应该避免双重求值

What is double evaluation and why should it be avoided?

本文关键字:为什么 什么      更新时间:2023-10-16

我正在阅读c++中使用的宏,如

#define max(a,b) (a > b ? a : b)

可以导致"双求值"。谁能给我举个例子,什么时候会发生双重求值,为什么它不好?

注::令人惊讶的是,我找不到任何详细的解释时,谷歌除了一个例子在Clojure(我不能理解)。

假设你写了这个:

#define Max(a,b) (a < b ? b : a)
int x(){ turnLeft();   return 0; }
int y(){ turnRight();  return 1; }

然后像这样调用它:

auto var = Max(x(), y());

你知道turnRight()将被执行两次吗?宏Max将扩展为:

auto var = (x() < y() ? y() : x());

在计算条件x() < y()之后,程序在y() : x()true之间进行所需的分支,它在第二次调用y() 。查看Live On Coliru

简单地说,将表达式作为参数传递给类函数的宏,Max可能会对该表达式求值两次,因为在宏定义中使用宏形参时,该表达式将被重复。记住,宏由预处理器处理。


所以,底线是,不要仅仅因为您希望它是泛型而使用宏来定义函数(在这种情况下实际上是一个表达式),而使用函数模板

可以有效地完成。

PS: c++有一个std::max模板函数

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

max(++i, 4);
如果i = 4在调用之前,

将返回6。由于这不是预期的行为,您应该选择内联函数来代替max这样的宏。

考虑以下表达式:

 x = max(Foo(), Bar());

其中FooBar如下:

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

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

 Foo() > Bar() ? Foo() : Bar();

无论哪种情况,Foo或Bar都将执行两次。因此花费的时间比必要的要长,或者改变程序状态的次数比预期的要多。在我的简单Bar示例中,它不会始终返回相同的值。

C和c++中的宏语言在"预处理"阶段由专用解析器处理;这些令牌被翻译,然后输出被适当地输入到解析器的输入流中。#define#include标记是C或c++解析器本身不能识别的

这很重要,因为它意味着当一个宏被"展开"时,它指的是字面上的。鉴于

#define MAX(A, B) (A > B ? A : B)
int i = 1, j = 2;
MAX(i, j);

c++解析器看到的是

(i > j ? i : j);

但是,如果我们将宏用于更复杂的内容,则会发生相同的展开:

MAX(i++, ++j);

展开为

(i++ > ++j ? i++ : ++j);

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

MAX(f(), g());

将扩展为

(f() > g() ? f() : g());

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

auto fret = f();
auto gret = g();
(fret > gret) ? fret : gret;

如果不能,则必须调用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());
}

实时演示:http://ideone.com/3JBAmF

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

相关文章: