以下是一个示例代码段:
int i = 4,b;
b = foo(i++) + foo(i++);
我很确定它是不未定义,因为在调用foo
之前有一个序列点。但是,如果我使用-Wall
标志编译代码,则会生成编译器警告,其中显示warning: operation on 'i' may be undefined
。我意识到它是may
,但我只想仔细检查一下我是否正确。
答案 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说:
如果标量对象的副作用相对于其中任何一个都没有排序 对同一个标量对象或值有不同的副作用 使用相同标量对象的值进行计算,行为是 未定义。如果有多个允许的排序 表达式的子表达式,如果这样的话,行为是不确定的 任何顺序都会出现无序的副作用。
将这些放在一起,符合要求的实现可以按此顺序评估表达式:
foo
并将值保存在某处。i++
并将值保存在某处。i++
,将第一个保存的值作为参数传递。foo
,将第二个保存的值作为参数传递。foo
。步骤1和2之间没有序列点,两者都修改b
,因此行为未定义。
(实际上这有点过于简单化;修改i
的副作用可以与i
的结果的确定分开。
底线:我们知道
i++
具有未定义的行为,原因已反复解释。在函数调用中包装b = i++ + i++;
子表达式会添加一些序列点,但这些序列点不会将i++
的两个评估分开,因此不会导致行为定义得很好。
即使是底线:请不要写那样的代码。即使行为得到了很好的定义,证明它并确定行为应该是更加困难。