在ANSI C中foo(i ++)+ foo(i ++)是否未定义?

这是一个示例代码段:

int i = 4,b; b = foo(i++) + foo(i++); 

我很确定它不是未定义的,因为在调用foo之前有一个序列点。 但是,如果我使用-Wall标志编译代码,则会生成编译器警告, warning: operation on 'i' may be undefined 。 我意识到它may ,但我只想仔细检查一下我是否正确。

行为未定义。

 b = foo(i++) + foo(i++); 

正如你所说,在第一个i++的评估和对foo的调用之间存在一个序列点,同样在第二个i++和call foo的评估之间也存在一个序列点。 但是,在i++的两次评估之间(或者更确切地说,在他们的副作用之间)(修改i )之间没有(必然)序列点。

引用2011年ISO C标准的N1570草案,第6.5.2.2p10节:

在评估函数指示符和实际参数之后但在实际调用之前有一个序列点。 调用函数(包括其他函数调用)中的每个评估(在执行被调用函数体之前或之后没有特别排序)在被调用函数的执行方面是不确定地排序的。

第二句在这里很重要:对于两个函数调用, i++的两个评估是“不确定地排序”,这意味着它们可以在调用foo之前或之后发生。 (但它们没有被排除 ;它们中的每一个都在调用之前或之后发生,但它未指定哪个。)

并且6.5p2说:

如果对标量对象的副作用相对于对同一标量对象的不同副作用或使用相同标量对象的值进行的值计算未进行排序,则行为未定义。 如果表达式的子表达式有多个允许的排序,则如果在任何排序中发生这种未测序的副作用,则行为是不确定的。

将这些放在一起,符合要求的实现可以按以下顺序评估表达式:

  1. 评估第一个i++并将值保存在某个地方。
  2. 评估第二个i++并将值保存在某处。
  3. 调用foo ,将第一个保存的值作为参数传递。
  4. 调用foo ,将第二个保存的值作为参数传递。
  5. 添加两个结果。
  6. 将总和存储在b

步骤1和2之间没有序列点,两者都修改i ,因此行为未定义。

(这实际上是一个轻微的过度简化;修改i的副作用可以与i++结果的确定分开。

底线:我们知道

 b = i++ + i++; 

由于已经反复解释的原因,它具有未定义的行为。 在函数调用中包装i++子表达式会添加一些序列点,但这些序列点不会将i++的两个评估分开,因此不会导致行为定义得很好。

甚至是底线:请不要写那样的代码。 即使行为得到了很好的定义,certificate它并确定行为应该是多么困难。