这组运算符是否会导致未定义的行为?

时间:2017-06-22 12:06:13

标签: c++ c language-lawyer

据我所知,C / C ++中不允许使用++i++之类的东西,因为它们破坏了一个规则,即变量不能在一个语句中多次写入。结果是未定义的行为。

有人可以确认这是否也适用于我的陈述:++i %= j

我想要一个for循环,我从i的任意点开始围绕循环缓冲区(大小j)递增p,直到我回到p

for(int i = p+1 ; i != p; ++i %= j  )
{
    if (buffer[i].ready()) 
    {
        buffer[i].do_something();
        p = i;
        break;
    }
}

我可以在更多代码行中完成,但不愿意。

5 个答案:

答案 0 :(得分:4)

在您的代码中,您说++i % j,这意味着'递增i(并将新值存储在i中),然后计算i和j的模数。但是这个值并没有存储在任何地方。

要获得环绕循环,您可以使用i = (i+1)%j

答案 1 :(得分:2)

在C ++ 17之前,行为未定义。

++i %= j相当于i = ++i % j

这是i = ++i的打扮版本,而每个人都知道这是UB。

答案 2 :(得分:2)

是的,您的代码定义明确(无论如何在C ++ 17中)。引用标准:

[expr.ass]

  

赋值运算符(=)和复合赋值运算符all   从右到左分组。所有都需要左侧可修改的左值   操作数并返回一个引用左操作数的左值。结果   在所有情况下,如果左操作数是位字段,则是位字段。 总之   在这种情况下,赋值在值的计算之后排序   左右操作数,以及之前的值计算   赋值表达式。 右操作数在左前排序   操作数。关于不确定顺序的函数调用,   复合赋值的操作是单一的评估。

[expr.pre.incr]

  

前缀++的操作数通过加1来修改。操作数应为   一个可修改的左值。操作数的类型应为算术运算   除了cv bool之外的类型,或者指向完全定义的对象的指针   类型。 结果是更新的操作数;它是一个左值,,它是一个   如果操作数是位字段,则为位字段。表达式++ x是   相当于x + = 1。

粗体文本表示您的代码几乎具有以下语义:

auto& __j = j;   // refer to j
auto& __i = ++i; // refer to i after the increment
__i %= __j;

如果标准保证,表达式看起来很可疑,您可以随时使用逗号运算符对其进行排序。

for(... ; ... ; (++i, i %= j))

答案 3 :(得分:2)

代码++i %= j与以下代码相同:

operator %= (++i, j);

在标准(§1.9/ 15)中,它表示

  

运算符操作数的值计算在运算符结果的值计算之前排序。

“价值计算”包括:

  • 值计算(包括确定glvalue评估对象的标识并获取先前分配给对象以进行prvalue评估的值)和
  • 引发副作用。

所以,在这里,编译器必须首先计算++ij(按任意顺序),包括++的副作用,并仅调用operator %=这些计算结束后。所以,至少从C ++ 11开始,这是一个明确定义的行为

有关详细信息,请参阅this answer

答案 4 :(得分:0)

其他人已经回答过你的代码是有效的C ++ 11及更高版本,但对于早期版本的C ++来说是未定义的行为,甚至不能用C编译(当然忽略类成员fcn调用)。所有这些都说你应该使用更明确的形式来实现你想要的目标。

我还想指出,p == j - 1代码可能会在您的代码中出现一个微妙的错误,因为您在初始化i时添加了一个但不是mod,这样您就可以访问那种情况下的数组。

您也永远不会处理索引p处的元素。那真的你想要什么?如果您反复执行此代码并且其他缓冲区都不是ready(),那么您将继续跳过检查是否buffer[p].ready()

更正确,更通用的代码(仍未检查索引p处的元素):

int i;

for (i = (p + 1) % j; i != p && !buffer[i].ready(); ++i, i %= j);

if (i != p)
{
  buffer[i].do_something();
  p = i;
}

通过处理索引p处的元素开始的版本,但在最多遍历一次数组后停止:

int i = p;

while (!buffer[i].ready() && (++i, i %= j, i != p));

if (buffer[i].ready())
{
  buffer[i].do_something();
  p = i;
}

上述版本有一个微妙的意义。如果!buffer[p].ready()在我们第一次进入循环时,我们遍历整个数组,其他元素都没有ready(),那么我们将使用i == p退出循环。然后,我们将再次询问buffer[p]是否为ready()。因此,可以两次询问buffer[p].ready(),这可能重要也可能不重要(例如 - 如果它有副作用)。抬头!

这是一个避免该问题的版本,但最后检查buffer[p].ready()

int i = p;

while ((++i, i %= j, i != p) && !buffer[i].ready());

if (i != p || buffer[p].ready())
{
  buffer[i].do_something();
  p = i;
}