为什么这(i = ++ i%3)会产生警告:“可能未定义”?
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
可能未定义?
正如其他人所指出的那样,行为是不确定的 :
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#和Java不同,做那些保证)。 例如,表达式++i
具有结果 ( i
+ 1)和副作用 (增加存储在i
的值); 但是,可以推迟副作用,直到评估出更大的表达。 IOW,允许以下一系列操作:
t0 = i + 1 t1 = t0%3 i = t1 i = i + 1
Oopsie。 不是我们想要的。
这是一个刻意的设计决定; 这个想法是它允许编译器以最佳方式重新排序评估(通过利用已经存在于寄存器中的值)。 缺点是某些表达式组合会产生不可预测的结果。
因为您正在修改i
的值两次而没有插入序列点 。 这是未定义的行为 。
在标准中,它是未定义的行为,因为i
被修改了两次没有插入序列点。
i = ++i % 3;
但这不是重点。 真正的问题是: 为什么有人会写这样的代码?!
你想要i
有什么价值? 如果你用i = ...
为i
分配一个全新的值,你试图用++i
实现什么效果? 如果这是parallel-universe-C,其中这样的代码实际上有意义,那么 – 在最好的情况下 – 递增的i
立即被替换为分配的全新值。 那么为什么不把它写成
i = (i+1) % 3;
这在C-as-know-know-it中也是正确的。