C语言运算符优先级与结合性疑问:表达式求值顺序解惑
C语言运算符优先级、求值顺序与未定义行为解析
先看你给出的示例代码:
#include <stdio.h> int main() { volatile int i = 10; i = i + i + i++ + (i *= 2); printf("%d\n", i); //输出52 return 0; }
你的核心误区是把运算符优先级/结合性和求值顺序混为一谈,这是C语言新手常犯的错误,下面逐一解释:
1. 优先级/结合性 ≠ 求值顺序
《The C Programming Language. 2nd Edition》里提到的优先级和结合性,只负责确定表达式的语法结构——也就是哪些操作数属于哪个运算符,完全不决定各个操作数的求值先后顺序。
比如你的表达式i + i + i++ + (i *= 2):
- 优先级决定
i++是独立操作数,(i *= 2)是独立子表达式,加法运算符是左结合(即先算左边的i+i,再和后面的结果相加); - 但这绝不意味着
i++或者(i *=2)必须先被求值,编译器可以选择任意顺序来计算各个加法操作数的值,只要最终符合语法结构的要求。
2. 括号的作用仅改变优先级,不强制求值顺序
你以为括号里的(i *=2)会优先执行,这是错误的。括号只是让i *=2作为一个整体子表达式,避免和其他运算符混淆,但C标准没有规定括号内的子表达式必须比其他操作数先求值。编译器完全可以先计算前两个i的值,再算i++,最后算括号里的(i *=2)——这就是你看到输出52的原因:
- 第一步:取第一个
i的值,为10; - 第二步:取第二个
i的值,还是10; - 第三步:计算
i++,取当前i的值10,之后i变为11; - 第四步:计算
(i *=2),此时i是11,乘2后变为22,返回值22; - 最后把所有值相加:10+10+10+22=52,赋值给
i,最终i就是52。
3. 你的表达式本质是未定义行为
按照C标准,在两个序列点(比如分号、逗号运算符等)之间,如果对同一个对象(这里是i)的存储值进行了多次修改,或者修改后又读取其值(除了用于计算修改后的值),这种行为是未定义的。
你用了volatile修饰i,虽然某些编译器会给出可预测的结果,但这并不符合C标准的规范——换个编译器或者编译选项,输出结果可能完全不同,甚至程序崩溃。
正确的做法
如果你想明确控制求值顺序,必须把表达式拆分成多个语句,每个语句只对i进行一次修改,比如:
#include <stdio.h> int main() { volatile int i = 10; int temp = i *= 2; // 先执行i*=2,i变为20,temp存20 int temp2 = i++; // 再执行i++,返回20,i变为21 i = i + i + temp2 + temp; // 21+21+20+20=82 printf("%d\n", i); return 0; }
这样每一步的顺序都由语句的执行顺序(从上到下)明确控制,结果是可预测的。
内容的提问来源于stack exchange,提问作者Daniel Bodyak
相关产品推荐
相关产品推荐

