C ++:在评估顺序运算符时忽略括号

时间:2017-01-25 22:56:55

标签: c++ comma parentheses g++4.8

首先,我必须说这里提出的问题已经解决了,我想知道:

  • 我误解了,
  • 如果编译器有错误(我知道这很少见)(它是gcc 4.8.4)。

我想计算二维向量的范数,其中坐标仅在那一刻计算。让我们说,我想计算|| (x0,y0) - (x1,y1)|| ,通过公式sqrt((x0-x1)** 2 +(y0-y1)** 2)。只需要保存结果。

出于性能原因,正方形是通过自乘法完成的,我希望变量和变量访问只能进行一次。我希望总计在运行时有效,并以某种方式优雅地编码。我想到了三种可能性:

  • 重复两次x0 - x1y0 - y1,并希望编译器的优化步骤能够检测到重复,
  • 使用内联函数
  • 一起使用缓冲变量和顺序运算符。

我决定尝试最后一个选项。

现在考虑以下代码:

#include <cmath>
#include <stdio.h>

int main (void)
{
    float x0 (-1), x1 (2), y0 (13), y1 (9), result, tmp;

    result = std::sqrt ((tmp = x0 - x1, tmp * tmp) + (tmp = y0 - y1, tmp * tmp));
    printf ("%f\n", result);
}

我知道我必须获得5.000000,但我获得5.656854,即sqrt((y0-y1)** 2 +((y0-y1)** 2))。

我可以通过以下方式获得想要的结果:

#include <cmath>
#include <stdio.h>

int main (void)
{
    float x0 (-1), x1 (2), y0 (13), y1 (9), result, tmp, tmp2;

    result = std::sqrt ((tmp = x0 - x1, tmp * tmp) + (tmp2 = y0 - y1, tmp2 * tmp2));
    printf ("%f\n", result);
}

首先评估顺序运算符的第一部分,忽略括号和第一个顺序运算符的返回值。似乎有点尴尬;我错过了C ++定义中的某些内容吗?

注意:在测试期间,打开或关闭优化设置不会改变任何内容。

2 个答案:

答案 0 :(得分:5)

可以交错评估operator +的两侧。特别是,括号中的两个赋值中的每一个必须在乘法到其右边(在正确的代码中,即第一个变量不是)之前发生,但不需要在另一个赋值之前发生。此外,允许在一个赋值和匹配乘法之间进行另一个赋值。

因此,您的第一个变体会调用未定义的行为,因为它包含两个未经序列的tmp修改。所以每个结果都是合法的,包括崩溃或NaN。

一般来说,请记住,#34;您的代码很聪明&#34;不是恭维。保持简单,如果你真的必须优化减法(你很可能不会):

auto dx = x0 - x1;
auto dy = y0 - y1;
auto result = std::sqrt(dx * dx + dy * dy);

答案 1 :(得分:1)

这一行:

result = std::sqrt ((tmp = x0 - x1, tmp * tmp) + (tmp = y0 - y1, tmp * tmp));

您应该避免修改在同一表达式中使用的值。大多数运算符不保证以任何特定顺序评估其操作数(例外是&amp;&amp;,||,?:和逗号运算符)。在这种情况下,此表达式中+运算符的操作数可以按任何顺序进行计算,而不必一次全部计算,因此您的代码具有未定义的行为。

此外,除非您已经对代码进行了分析并且知道您需要对特定语句进行严格优化,否则您应该更清楚明智而不是巧妙的技巧。