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

Jus*_*tin 3 c undefined-behavior sequence-points

这是一个示例代码段:

int i = 4,b;    
b = foo(i++) + foo(i++);
Run Code Online (Sandbox Code Playgroud)

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

Kei*_*son 7

行为未定义.

b = foo(i++) + foo(i++);
Run Code Online (Sandbox Code Playgroud)

正如您所说,在第一次评估和第二次评估i++与呼叫之间存在一个序列点foo,同样在评估第二次i++和呼叫之间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++;
Run Code Online (Sandbox Code Playgroud)

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

甚至是底线:请不要写那样的代码.即使行为得到了很好的定义,证明它并确定行为应该是更加困难.