原始题目
考察函数执行过程:以下代码的输出是什么?
int a = 0;
int f(int x, int y) {
return x + y;
}
int main() {
cout << f(++a, ++a);
return 0;
}
- A. 1
- B. 2
- C. 3
- D. 未定义行为
答案:D(未定义行为)
直觉为什么会出错
很多人会这样推:a=0,两次 ++a,a 变成 2,两个参数是 1 和 2,输出 3——选 C。
这个推理有一个隐藏前提:“先算第一个参数,再算第二个参数”。但 C++ 标准从来没有做过这个承诺。
关键规则:实参求值顺序是无序的
函数实参之间的求值顺序是不确定的(unsequenced)。C++ 标准不保证
f(++a, ++a)里先算哪个参数。GCC 在 x86 上常从右往左算,其他编译器可能从左往右。更严重的是:这不仅是“顺序不确定”,而是未定义行为(UB)。两次
++a是对同一个变量a的两个副作用,且这两个副作用之间无序——C++ 标准规定:同一标量对象上两个无序的副作用,程序行为未定义。编译器可以生成任何代码、产生任何结果,甚至崩溃。实际运行结果千差万别:
- GCC/x86 从右往左:两个参数都算完才传值,参数是 (2, 2),输出 4
- 某些编译器从左往右:参数是 (1, 2),输出 3
- 开优化后还可能输出完全不同的值
注意选项里根本没有 4 —— 这本身就是出题人的提示:想选出“确定值”就已经掉坑了。
// g++ 10 实测(典型结果,不可依赖)
cout << f(++a, ++a); // 常见输出 4,而不是 3
易混淆对比
| 写法 | 是否合法 | 说明 |
|---|---|---|
f(++a, ++a) |
❌ UB | 同一变量两次无序修改 |
f(a+1, a+1) |
✅ 合法 | 只读取 a,不修改,输出 2 |
int b = a; f(++b, ++b); |
✅ 合法 | 修改的是不同对象 b,输出 3 |
cout << ++a << ++a; |
❌ UB | 同理,<< 之间对 a 的多个修改也无序 |
常见的相关 UB(同一考点家族)
int i = 0;
cout << i++ << " " << i++; // UB:两个无序副作用
i = i++ + 1; // UB(C++17 前;C++17 起右侧先完全求值,已合法)
int j = i++ * i++; // UB
C++17 引入了部分求值顺序规则(如函数实参求值虽然彼此无序,但整体先于函数体执行),但实参之间依然无序,所以
f(++a, ++a)在 C++17 及之后仍然是 UB。
考点总结(CSP-J 初赛选择题)
- 功能:考察“序列点 / 求值顺序”概念,不是考递归也不是考函数调用栈。
- 结论:在同一个表达式里对同一变量修改多次(且没有序列点隔开)→ 未定义行为,答案选 D。
- 秒杀技巧:选择题里看到
f(++a, ++a)、i++ + ++i、cout << i++ << i++这类“一个表达式里反复自增同一个变量”,直接选未定义行为,不用模拟。 - 正确写法:把修改拆到不同语句里:
int a = 0;
++a; // 先改
int x = a; // 再用
++a;
cout << f(x, a); // 输出 3,清晰无歧义
思考题
cout << ++a + ++a;(a=0)的输出是确定的吗?
答案
不是确定值——同样是未定义行为。两个 ++a 对同一变量无序修改,任何输出都可能。



