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

时间:2014-01-09 21:25:09

标签: c undefined-behavior sequence-points

以下是一个示例代码段:

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

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

1 个答案:

答案 0 :(得分:7)

行为未定义。

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

正如您所说,第一个i++的评估与foo的调用之间存在一个序列点,同样在第二个i++的评估与调用{{之间1}}。但是,foo的两次评估之间没有(必然)序列点,或者更具体地说是它们的副作用之间(修改i++)。

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

  

在评估函数后有一个序列点   指定者和实际参数但在实际调用之前。一切   调用函数中的求值(包括其他函数调用)   在此之前或之后没有特别排序的   被调用函数体的执行是不确定的   关于被调用函数的执行顺序。

第二句在这里意义重大:i的两个评价是“不确定的 对“两个函数调用”进行了排序,这意味着它们可以在调用i++之前或之后发生。(它们不是未排序的;它们中的每一个都发生在之前或者在电话会议之后,但未指明哪个。)

6.5p2说:

  

如果标量对象的副作用相对于其中任何一个都没有排序   对同一个标量对象或值有不同的副作用   使用相同标量对象的值进行计算,行为是   未定义。如果有多个允许的排序   表达式的子表达式,如果这样的话,行为是不确定的   任何顺序都会出现无序的副作用。

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

  1. 评估第一个foo并将值保存在某处。
  2. 评估第二个i++并将值保存在某处。
  3. 调用i++,将第一个保存的值作为参数传递。
  4. 调用foo,将第二个保存的值作为参数传递。
  5. 添加两个结果。
  6. 将总和存储在foo
  7. 步骤1和2之间没有序列点,两者都修改b,因此行为未定义。

    (实际上这有点过于简单化;修改i的副作用可以与i的结果的确定分开。

    底线:我们知道

    i++

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

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