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