C++表达式求值顺序

4

我遇到一个有趣的问题,涉及表达式的评估:

reference operator()(size_type i, size_type j) {
  return by_index(i, j, index)(i, j); // return matrix index reference with changed i, j
}

matrix& by_index(size_type &i, size_type &j, index_vector &index) {
  size_type a = position(i, index); // find position of i using std::upper_bound
  size_type b = position(j, index);
  i -= index[a];
  j -= index[b];
  return matrix_(a,b); // returns matrix reference stored in 2-D array
}

我曾认为矩阵(i,j)会在调用buy_index后被评估,以便更新i,j。在调试器中验证后,这似乎是正确的。然而,对于某些类型的矩阵,特别是那些必须将size_type强制转换为其他类型(例如int)的矩阵,在by_index中的更新会丢失。稍微修改代码即可解决问题:

reference operator()(size_type i, size_type j) {
  matrix &m = by_index(i, j, index);
  return m(i, j); 
}

你知道为什么第一个运算符会出现问题吗?

谢谢。

哪些原型有效,哪些无效

inline reference operator () (size_t i, size_t j); // ublas, size_type is std::size_t
reference operator () (int i, int j); // other prototype, size_type is int

在调试器的回溯堆栈中,看起来是这样的:

  • 进入 operator() 时 i = 1 //没问题
  • 从 by_index 完成后 i = 0 //没问题
  • 进入 matrix::operator() 时 i = 1 //不对,应该是 0

1
你能给出一个需要转换 size_type 的代码示例吗?我有点困惑,但我认为这可能是你的问题所在。(与转换有关的问题会影响你的引用传递。) - SoapBox
矩阵_(a,b)的定义是什么? 我推测它返回一个矩阵&,但它从哪里获取呢? - John Weldon
那么,position(i, index)的定义是什么? - John Weldon
我在代码中添加了注释来解释函数的功能,它们通过值或者使用const引用来接收参数。这些函数不应该有副作用。 - Anycorn
这最后两个原型看起来对我来说是一模一样的... - John Weldon
5个回答

3
在我看来,这取决于评估的顺序。
标准规定如下:
(5.4)除非另有说明,否则操作数和子表达式的评估顺序以及副作用发生的顺序是未指定的。
这完全符合要求。i和j的值可能在调用by_index()之前或之后被评估。你无法确定 - 这是未指定的。
我想补充的是,解决您问题的形式在我看来要更易读得多,即使第一种形式不正确,我也会使用它...

好的,这就是我在遇到问题之后开始思考的。在那之前,我认为顺序是有保证的。 - Anycorn
@jk,实际上,我怀疑序列点在这里是否相关。在从by_index()函数调用返回后确实有一个序列点。但参数本身的评估不受序列点的影响。 - Hexagon

2
我怀疑将引用转换为不同类型会违反编译器使用的严格别名规则,这些规则可更有效地进行优化。你有两个不同类型的变量/引用,编译器假设它们不引用同一内存(但实际上它们确实引用同一内存)。然后编译器在这个错误的假设下对代码进行优化,从而产生错误的结果。

你可以尝试使用-fno-strict-aliasing(或等效选项)进行编译,以禁用这些优化,并查看是否改善了情况。


我一直在使用 g++ -O0 -Wall 进行编译。我认为这会禁用所有潜在的危险优化。我会尝试你的建议。 - Anycorn
嗯,-O0 应该已经关闭了它,所以这可能不是解决方案。虽然症状很符合。 - sth

2

最后,我在标准中找到了规定此事的地方(草案n1905):

(5.2.2-8) - 参数的评估顺序是未指定的。所有参数表达式计算的副作用在函数进入之前生效。后缀表达式和参数表达式列表的评估顺序是未指定的。

提到的后缀表达式()左侧的部分。因此,在“外部”函数调用中,无法确定先评估by_index(i, j, index)还是它的参数(i, j)

函数返回后存在一个序列点,因此当by_index(i, j, index)返回时,所有副作用都已完成,但是(i, j)参数可能已经被评估(并且值已存储在寄存器或其他地方)在该函数被调用之前。


0
通过参数列表传递数据非常不清晰。依赖于引用类型之间的隐式转换是非常不安全的。
无论如何,如果你使用一个int参数调用by_index(size_t &,…,那么你正在取一个临时变量的引用。这应该是一个警告,但也许你在使用旧编译器。尝试使用显式转换。
reinterpret_cast< size_t & >( i ) /* modify i before next fn call */

当然,这并不能保证做正确的事情。实际上,你需要区分有符号和无符号,并且不要假设 sizeof(int) == sizeof(size_t),因为通常情况下它们是不相等的。


0
作为额外的说明,这是典型情况,其中简洁优于清晰,Brian Kernighan 强烈建议我们避免这种情况(他在这些问题上写了一本优秀的书籍《编程实践》)。在这样的代码中,评估顺序没有很好地定义,导致“副作用”不可预测的结果。您所做的更改是应对这种情况的推荐方法。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接