int main(void)
{
int i = 0;
i = ++i % 3;
return 0;
}
我这样编译:
$ gcc -Wall main.c -o main
main.c: In function ‘main’:
main.c:4: warning: operation on ‘i’ may be undefined
为什么编译器说i
可能未定义?
答案 0 :(得分:11)
因为您在没有干预sequence point的情况下两次修改i
的值。这是undefined behavior。
答案 1 :(得分:6)
在标准中,它是未定义的行为,因为i
被修改两次没有插入序列点。
i = ++i % 3;
但这不是重点。真正的重点是:为什么有人会写这样的代码?!
您希望i
拥有什么价值?如果您使用i
将全新值分配到i = ...
,那么您尝试使用++i
会产生什么影响?如果这是parallel-universe-C,其中这样的代码实际上有意义,那么 - 在最好的情况下 - 递增的i
会立即替换为分配的全新值。那么为什么不把它写成
i = (i+1) % 3;
在C-as-know-know-it中也是正确的。
答案 2 :(得分:4)
正如其他人所指出的,行为是undefined:
6.5表达
...
2在前一个和下一个序列点之间,一个对象应具有其存储值 通过表达式的评估最多修改一次。 72)此外,先前的值 只读以确定要存储的值。 73)
...
72)浮点状态标志不是对象,可以在表达式中多次设置。 73)此段落呈现未定义的语句表达式,例如
i = ++i + 1; a[i++] = i;允许的同时i = i + 1; a[i] = i;
表达式i = ++i % 3
尝试在下一个序列点之前修改i
中包含的值(在这种情况下,结束语句的;
) ,一次评估++i
,一次评估更大的任务表达式。
现在,为什么这会成为问题?毕竟,C#和Java可以很好地处理这些表达式。
问题在于,除了极少数例外,C不保证表达式中的操作数以任何特定顺序进行计算,或者表达式的副作用将在计算表达式后立即应用(与C#和C#不同) Java,确实做出了这些保证)。例如,表达式++i
具有结果(i
+ 1)和副作用(增加{{1}中存储的值});但是,可以推迟副作用,直到评估出更大的表达。 IOW,允许以下一系列操作:
t0 = i + 1 t1 = t0 % 3 i = t1 i = i + 1
Oopsie。不是我们想要的。
这是一个刻意的设计决定;这个想法是它允许编译器以最佳方式重新排序评估(通过利用已经存在于寄存器中的值)。缺点是某些表达式组合会产生不可预测的结果。