C++:评估顺序运算符时忽略括号
C++ : parentheses ignored when evaluating sequential operators
首先,我必须说这里提出的问题已经为我解决了,我想知道:
- 我误会了什么,
- 如果编译器出错(我知道这种情况很少见)(是gcc 4.8.4)。
我想计算一个二维向量的范数,它的坐标只在那个时刻计算。比方说,我想计算 || (x0, y0) - (x1, y1) || ,通过公式 sqrt((x0-x1)**2 + (y0-y1)**2) 。只需要保存结果。
出于性能原因,平方是通过自乘来完成的,我希望减法和对变量的访问只进行一次。我希望总计在运行时高效,并且以某种方式优雅地编码。我想到了三种可能:
- 重复两次
x0 - x1
和y0 - 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);
}
这就像首先评估顺序运算符的第一部分,忽略括号和第一个顺序运算符的 return 值。似乎有点尴尬;我在这里错过了 C++ 定义中的某些内容吗?
注意:在测试期间将优化设置为开或关不会改变任何内容。
operator +
的两边可以交错求值。特别是,括号中的两个赋值中的每一个都必须在其右侧的乘法之前发生(在正确的代码中,即您的第一个变体不是),但不必在另一个赋值之前发生。此外,允许在一个赋值和匹配的乘法之间发生另一个赋值。
因此,您的第一个变体调用了未定义的行为,因为它包含 tmp
的两个未排序的修改。所以从字面上看,每个结果都是合法的,包括崩溃或 NaN。
一般来说,请记住 "your code is clever" 不是恭维。保持简单,如果你真的必须优化减法(你很可能不需要):
auto dx = x0 - x1;
auto dy = y0 - y1;
auto result = std::sqrt(dx * dx + dy * dy);
这一行:
result = std::sqrt ((tmp = x0 - x1, tmp * tmp) + (tmp = y0 - y1, tmp * tmp));
您应该避免修改您在同一表达式中别处使用的值。大多数运算符不能保证以任何特定顺序评估其操作数(例外情况是 &&、||、?: 和逗号运算符)。在这种情况下,此表达式中 + 运算符的操作数可以按任何顺序求值,而不必一次全部求值,因此您的代码具有未定义的行为。
此外,除非您分析了您的代码并且知道您需要对特定语句进行严格优化,否则您应该更喜欢清晰而不是巧妙的技巧。
首先,我必须说这里提出的问题已经为我解决了,我想知道:
- 我误会了什么,
- 如果编译器出错(我知道这种情况很少见)(是gcc 4.8.4)。
我想计算一个二维向量的范数,它的坐标只在那个时刻计算。比方说,我想计算 || (x0, y0) - (x1, y1) || ,通过公式 sqrt((x0-x1)**2 + (y0-y1)**2) 。只需要保存结果。
出于性能原因,平方是通过自乘来完成的,我希望减法和对变量的访问只进行一次。我希望总计在运行时高效,并且以某种方式优雅地编码。我想到了三种可能:
- 重复两次
x0 - x1
和y0 - 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);
}
这就像首先评估顺序运算符的第一部分,忽略括号和第一个顺序运算符的 return 值。似乎有点尴尬;我在这里错过了 C++ 定义中的某些内容吗?
注意:在测试期间将优化设置为开或关不会改变任何内容。
operator +
的两边可以交错求值。特别是,括号中的两个赋值中的每一个都必须在其右侧的乘法之前发生(在正确的代码中,即您的第一个变体不是),但不必在另一个赋值之前发生。此外,允许在一个赋值和匹配的乘法之间发生另一个赋值。
因此,您的第一个变体调用了未定义的行为,因为它包含 tmp
的两个未排序的修改。所以从字面上看,每个结果都是合法的,包括崩溃或 NaN。
一般来说,请记住 "your code is clever" 不是恭维。保持简单,如果你真的必须优化减法(你很可能不需要):
auto dx = x0 - x1;
auto dy = y0 - y1;
auto result = std::sqrt(dx * dx + dy * dy);
这一行:
result = std::sqrt ((tmp = x0 - x1, tmp * tmp) + (tmp = y0 - y1, tmp * tmp));
您应该避免修改您在同一表达式中别处使用的值。大多数运算符不能保证以任何特定顺序评估其操作数(例外情况是 &&、||、?: 和逗号运算符)。在这种情况下,此表达式中 + 运算符的操作数可以按任何顺序求值,而不必一次全部求值,因此您的代码具有未定义的行为。
此外,除非您分析了您的代码并且知道您需要对特定语句进行严格优化,否则您应该更喜欢清晰而不是巧妙的技巧。