这是一个示例片段:
int i = 4,b;
b = foo(i++) + foo(i++);
我很确定它不是未定义的,因为在调用foo
. 但是,如果我使用-Wall
标志编译代码,则会生成编译器警告,上面写着warning: operation on 'i' may be undefined
. 我意识到它说may
,但我只是想仔细检查一下我是否正确。
这是一个示例片段:
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++
和对的调用之间也是如此foo
。i++
但是在 的两个评估之间,或者更具体地说,在它们的副作用(修改)之间没有(必然)一个序列点i
。
引用 2011 ISO C 标准的N1570草案,第 6.5.2.2p10 节:
在函数指示符和实际参数的评估之后但在实际调用之前有一个序列点。调用函数(包括其他函数调用)中的每个求值,如果在被调用函数的主体执行之前或之后没有特别排序,则相对于被调用函数的执行是不确定的。
第二句话在这里很重要:i++
关于两个函数调用的两个评估是“不确定顺序的”,这意味着它们可以在调用之前或之后发生foo
。(不过,它们不是unsequenced;它们中的每一个都发生在调用之前或之后,但未指定哪个。)
6.5p2 说:
如果标量对象上的副作用相对于同一标量对象上的不同副作用或使用相同标量对象的值的值计算是未排序的,则行为未定义。如果表达式的子表达式有多个允许的排序,则如果在任何排序中出现这种未排序的副作用,则行为未定义。
综上所述,符合要求的实现可以按以下顺序评估表达式:
i++
并将值保存在某处。i++
并将值保存在某处。foo
,将第一个保存的值作为参数传递。foo
,将第二个保存的值作为参数传递。b
.步骤 1 和 2 之间没有序列点,两者都是 modify i
,因此行为未定义。
(这实际上有点过于简单化了;修改的副作用i
可以与i++
.
底线:我们知道
b = i++ + i++;
具有未定义的行为,原因已被反复解释。在函数调用中包装i++
子表达式确实会添加一些序列点,但这些序列点不会将 的两个评估分开i++
,因此不会导致行为变得明确。
甚至是底线:请不要编写那样的代码。即使行为被很好地定义,证明它并确定行为应该是什么也比值得更困难。