C语言中表达式与函数实参的执行顺序为何存在差异?
这问题问得非常到位,刚好戳中了C语言里一个极易踩坑的细节——函数实参和二元运算符操作数的求值顺序,在C标准里是「未指定行为」,不是你以为的固定从左到右或者从右到左。
先拆解你的两段代码:
先看Code1的情况
#include <stdio.h> int main() { printf(" %d %d",printf("Hello"),printf("Bye")); return 0; }
这里printf一共接收3个实参:格式字符串" %d %d"、printf("Hello")、printf("Bye")。C标准完全没有规定函数实参的求值顺序——编译器可以选择先算最右边的实参,也可以先算中间的,甚至乱序(只要最终把正确的值传给函数就行)。你的编译器在这里选择了先执行最右边的printf("Bye"),再执行中间的printf("Hello"),最后处理格式字符串,所以输出是ByeHello 5 3(printf的返回值是打印的字符数,"Hello"是5个字符,"Bye"是3个)。
再看Code2的情况
#include <stdio.h> int main() { printf(" %d",printf("Hello")+printf("Bye")); return 0; }
这里printf的实参是两个:格式字符串" %d",以及加法表达式printf("Hello")+printf("Bye")。注意,加法运算符+的两个操作数(也就是两个printf调用)的求值顺序,C标准同样是未指定的——编译器可以先算左边的printf("Hello"),也可以先算右边的。你的编译器这次选择了先左后右,所以先输出Hello再输出Bye,两个返回值5+3=8,最后输出 8,整体就是HelloBye 8。
核心规则总结
- 函数实参的求值顺序是未指定行为:标准不要求编译器按左到右或右到左执行,不同编译器、不同优化级别都可能有不同选择。
- *二元运算符(+、-、等)的操作数求值顺序也是未指定的:同样没有固定顺序,别依赖它。
- 只有「序列点」能保证执行顺序:比如分号
;、逗号运算符(注意不是函数参数里的逗号!)、逻辑与&&(左边为假时右边不执行)、逻辑或||(左边为真时右边不执行)、条件运算符?:等,这些位置会保证左边的代码完全执行完毕后,才会执行右边的代码。
举个例子,如果想强制固定顺序,应该用序列点拆分:
#include <stdio.h> int main() { int hello_len = printf("Hello"); int bye_len = printf("Bye"); printf(" %d %d", hello_len, bye_len); return 0; }
这里分号是序列点,所以一定会先执行printf("Hello"),再执行printf("Bye"),输出固定为HelloBye 5 3。
重要提醒
永远不要依赖未指定的求值顺序写代码!这种代码在不同环境下的行为完全不可预测,会导致严重的可移植性问题,甚至出现难以调试的bug。
内容的提问来源于stack exchange,提问作者S_D

