C语言中stack[++top]这类数组下标含前置自增的表达式是否真的属于未定义行为?
这问题问得特别好——我刚学C的时候也纠结过这个点,很多看起来“常用”的写法背后其实藏着标准的坑,咱们一步步理清楚:
C标准里确实存在未定义行为的情况,这类代码的行为不被标准保证,不同编译器甚至同一编译器的不同优化等级都可能给出不同结果,绝对不能依赖“现代编译器都支持”这种假设——标准没规定的,编译器想怎么实现都可以,今天能跑的代码明天换个编译环境就可能崩。
要搞懂这个,得先明白C标准里的序列点概念:它是程序执行过程中的“节点”,在这个节点之前,所有表达式的副作用(比如变量自增、赋值)必须全部完成;节点之后的副作用还没开始。
如果在两个序列点之间,对同一个变量同时做了修改操作和无关联的读取操作,或者多次修改同一个变量,那这段代码的行为就是未定义的——编译器可以任意选择执行顺序,结果完全不可控。
a[i++] = i;是未定义行为? 咱们拿这个例子拆解:假设i初始值是2。
- 编译器可以选择先计算赋值号右边的
i,得到2,再执行左边的i++(i变成3),最终结果是a[2] = 2; - 也可以选择先执行左边的
i++(i变成3),再计算右边的i得到3,最终结果是a[2] = 3;
这两种结果都符合C标准,因为赋值运算符的左右两边之间没有序列点隔开——标准没规定哪边先算。你根本没法预测代码会输出什么,这就是未定义行为的可怕之处。
stack[++top] = new_value;为什么是安全的? 别急,不是所有带自增的下标写法都有问题!这个例子里,赋值号右边的new_value和top没有任何关联,不管编译器先算哪边:
- 先算
++top:top自增后,把new_value放到新的栈顶位置; - 先算
new_value:拿到值后,再执行++top,然后把值放到新的栈顶位置;
两种顺序的结果完全一致,所以这个写法是标准明确有定义的,不属于编码规范禁止的“复杂方式使用带副作用的表达式”。
++i和i++的意义依然存在 这和未定义行为是两码事:
++i(前置自增):先把变量加1,再返回加后的值;i++(后置自增):先返回变量当前的值,再把变量加1;
这两个操作的语义是标准明确规定的,只要你不在同一个序列点内对同一个变量同时做“修改+无关联读取”,就完全合法。比如:
- 合法场景:
int next = ++top;(next拿到自增后的栈顶索引)、int old_top = top++;(old_top拿到自增前的栈顶索引); - 非法场景:
int sum = i++ + ++i;(同一个表达式里多次修改i,未定义行为);
编码规范禁止的是滥用副作用,而不是禁止所有带自增/自减的表达式——合理使用++i和i++依然是C语言里非常实用的语法。
内容的提问来源于stack exchange,提问作者user14189755

