序列点在c
命令式编程中的序列点定义了计算机程序执行中的任何点,在该点处保证先前评估的所有副作用都将被执行,并且尚未执行后续评估的副作用。
这是什么意思? 有人可以用简单的语言解释一下吗?
当序列点出现时,它基本上意味着您可以保证所有先前的操作都已完成。
在没有插入序列点的情况下更改变量两次是未定义行为的一个示例。
例如, i = i++;
是未定义的,因为i
的两个变化之间没有序列点。
维基百科有一个C和C ++标准中的序列点列表,尽管最终列表应始终取自ISO标准。 来自C99附录C:
以下是5.1.2.3中描述的序列点:
- 在评估参数之后调用函数(6.5.2.2)。
- 以下运算符的第一个操作数的结尾:logical AND &&(6.5.13); 逻辑OR || (6.5.14); 条件? (6.5.15); 逗号,(6.5.17)。
- 完整声明者的结尾:声明者(6.7.5);
- 完整表达式的结束:初始化器(6.7.8); 表达式中的表达式(6.8.3); 选择语句的控制表达式(if或switch)(6.8.4); while或do语句的控制表达式(6.8.5); for语句的每个表达式(6.8.5.3); 返回语句中的表达式(6.8.6.4)。
- 紧接库函数返回之前(7.1.4)。
- 在与每个格式化的输入/输出函数转换说明符(7.19.6,7.24.2)相关联的操作之后。
- 紧接在每次调用比较函数之前和之后,以及对比较函数的任何调用和作为参数传递的对象的任何移动之间的紧接之间(7.20.5)。
C11改变了措辞。 它似乎打破了三元运算符并添加了一些更多细节:
以下是5.1.2.3中描述的序列点:
- 在函数调用和实际调用中的函数指示符和实际参数的评估之间。 (6.5.2.2)。
- 在以下运算符的第一个和第二个操作数的计算之间:逻辑AND &&(6.5.13); 逻辑OR || (6.5.14); 逗号,(6.5.17)。
- 在条件?:运算符的第一个操作数的评估与第二个和第三个操作数中的任何一个之间进行评估(6.5.15)。
- 完整声明者的结尾:声明者(6.7.6);
- 在评估完整表达式和下一个要评估的完整表达式之间。 以下是完整表达式:初始化程序(6.7.9); 表达式中的表达式(6.8.3); 选择语句的控制表达式(if或switch)(6.8.4); while或do语句的控制表达式(6.8.5); for语句的每个表达式(6.8.5.3); 返回语句中的表达式(6.8.6.4)。
- 紧接库函数返回之前(7.1.4)。
- 在与每个格式化的输入/输出函数转换说明符(7.21.6,7.28.2)相关联的操作之后。
- 紧接在每次调用比较函数之前和之后,以及对比较函数的任何调用和作为参数传递给该调用的对象的任何移动之间(7.22.5)。
关于序列点的一个重要注意事项是它们不是全局的,而应该被视为一组局部约束。 例如,在声明中
a = f1(x ++)+ f2(y ++);
在评估x ++和调用f1之间有一个序列点,在y ++的评估和对f2的调用之间有另一个序列点。 但是,不能保证在调用f2之前或之后x是否会递增,也不能保证y在调用x之前或之后是否递增。 如果f1改变y或f2改变x,则结果将是未定义的(对于编译器生成的代码来说,例如读取x和y,递增x,调用f1,对照先前读取的值检查y,以及 – 如果它改变了 – 继续横冲直撞寻找并摧毁所有Barneyvideo和商品;我认为任何真正的编译器都不会产生实际上会这样做的代码,唉,但它会被允许在标准之下)。
通过一个例子扩展了paxdiablo的答案。
假设声明
x = i++ * ++j;
有三个副作用:将i * (j+1)
的结果分配给x,将i加1,并将1加1。 副作用的应用顺序是未指定的; i和j可以在被评估之后立即递增,或者它们可以不被递增,直到已经评估了两个之后但是在分配了x之前,或者它们可以在x被分配之后才递增。
序列点是已应用所有副作用的点(x,i和j都已更新),无论它们的应用顺序如何。
这意味着编译器可以进行时髦的优化,技巧和魔术,但必须在这些所谓的序列点处达到明确定义的状态。