什么是“序列点”?
未定义的行为和序列点之间的关系是什么?
我经常使用像_284620这样有趣而复杂的表达方式来让自己感觉更好 . 我为什么要停止使用它们?
如果您已阅读此内容,请务必访问后续问题Undefined behavior and sequence points reloaded .
(注意:这是Stack Overflow的C FAQ的一个条目 . 如果你想批评提供这种形式的常见问题解答的想法,那么发布所有这些的meta上的帖子就是这样做的地方 . 这个问题在C聊天室中受到监控,其中FAQ的想法首先出现在那里,所以你的答案很可能被那些想出这个想法的人阅读 . )
5 回答
我猜这个改变有一个根本原因,让旧的解释更清晰,不仅仅是装饰性的:原因是并发性 . 未详细说明的顺序仅仅是选择几个可能的连续排序中的一个,这与排序之前和之后完全不同,因为如果没有指定的排序,则可以进行并发评估:旧规则不是这样 . 例如:
先前要么是b,要么是b,然后是a . 现在,可以使用交错的指令或甚至在不同的核上评估a和b .
C 98和C 03
这个答案适用于旧版本的C标准 . 标准的C 11和C 14版本没有正式包含“序列点”;操作是“先排序”或“未排序”或“不确定排序” . 净效果基本相同,但术语不同 .
Disclaimer :好的 . 这个答案有点长 . 所以在阅读时要有耐心 . 如果你已经知道这些事情,再次阅读它们不会让你发疯 .
Pre-requisites :C++ Standard的基本知识
什么是序列点?
标准说
副作用?有什么副作用?
表达式的评估产生一些东西,如果另外执行环境的状态发生变化,则表示表达式(其评估)具有一些副作用 .
例如:
除初始化操作外,由于
++
运算符的副作用,y
的值也会发生变化 .到现在为止还挺好 . 继续前进到序列点 . comp.lang.c作者
Steve Summit
给出的seq-points的交替定义:C标准中列出的常见序列点是什么?
那些是:
§1.9/16
)(完整表达式是一个不是另一个表达式的子表达式的表达式 . )1示例:
在评估第一个表达式(
§1.9/18
)2后评估下列每个表达式a && b (§5.14)
a || b (§5.15)
a ? b : c (§5.16)
a , b (§5.18)
(这里a,b是逗号运算符;在func(a,a++)
,
中不是逗号运算符,它只是参数a
和a++
之间的分隔符 . 因此在这种情况下行为是未定义的(如果a
被认为是基本类型) ))在函数调用(无论函数是否为内联函数)之后,在评估函数体(
§1.9/17
)中执行任何表达式或语句之前发生的所有函数参数(如果有)之后 .1:注意:对完整表达式的评估可以包括对词性表达式的评估,这些子表达式不是词法表达式的全部表达式 . 例如,在计算默认参数表达式(8.3.6)时涉及的子表达式被认为是在调用函数的表达式中创建的,而不是在定义默认参数的表达式中创建的
2:所指示的运算符是内置运算符,如第5节所述 . 当其中一个运算符在有效上下文中重载(第13节),从而指定用户定义的运算符函数时,表达式指定函数调用和操作数形成一个参数列表,它们之间没有隐含的序列点 .
什么是未定义的行为?
标准将
§1.3.12
中的未定义行为定义为3:允许的未定义行为范围从完全忽略不完全结果的情况,到翻译或程序执行期间的行为,以文件的特征环境(有或没有发出诊断消息),终止翻译或执行(发布诊断消息) .
简而言之,未定义的行为意味着从你的鼻子飞到你的女朋友怀孕的守护进程可能发生 anything .
未定义行为和序列点之间的关系是什么?
在我进入之前你必须知道Undefined Behaviour, Unspecified Behaviour and Implementation Defined Behaviour之间的差异 .
你也必须知道
the order of evaluation of operands of individual operators and subexpressions of individual expressions, and the order in which side effects take place, is unspecified
.例如:
另一个例子here .
现在
§5/4
中的标准说这是什么意思?
非正式地,它意味着在两个序列点之间不能多次修改变量 . 在表达式语句中,
next sequence point
通常位于终止分号处,previous sequence point
位于前一个语句的末尾 . 表达式也可能包含中间sequence points
.从上面的句子中,以下表达式调用未定义的行为:
但是下面的表达式很好:
这是什么意思?这意味着如果一个对象被写入一个完整的表达式,则在同一个表达式 must be directly involved in the computation of the value to be written 中对它进行任何和所有访问 .
例如,在
i = i + 1
中,i
(在L.H.S和R.H.S中)的所有访问都是要写入的值的 directly involved in computation . 所以很好 .此规则有效地将法律表达式约束为在修改之前明显存在访问的表达式 .
例1:
例2:
是不允许的,因为
i
(a[i]
中的那个)的访问之一与最终存储在i中的值无关(在i++
中发生),所以's no good way to define--either for our understanding or the compiler' s - 是否应该进行访问在存储增量值之前或之后 . 所以行为是不确定的 .例3:
Follow up answer for C++11 here.
在
C99(ISO/IEC 9899:TC3)
中,到目前为止,这个讨论中似乎没有出现以下关于评估顺序的受访者 .This is a follow up to my previous answer and contains C++11 related material. .
Pre-requisites :关系(数学)的基础知识 .
C 11中没有序列点是真的吗?
Yes! 这是真的 .
Sequence Points 已被C11中的 Sequenced Before 和 Sequenced After (以及 Unsequenced 和 Indeterminately Sequenced )relations替换 .
究竟是什么'之前排序'的事情?
Sequenced Before (§1.9/ 13)是一种关系:
Asymmetric
Transitive
由单个thread执行的评估之间,并诱导 strict partial order 1
形式上它意味着给出任何两个评估(见下文)
A
和B
,如果A
是 sequenced beforeB
,那么A
的执行应该在执行B
之前 . 如果在B
之前未对A
进行排序,并且在A
之前未对B
进行排序,则A
和B
为 unsequenced 2 .当在
B
之前对A
进行排序或在A
之前对B
进行排序时,评估A
和B
为 indeterminately sequenced ,但未指定哪个为3 .[注] 1:严格的偏序是在集合P上的二元关系“<”,它是不对称的,并且是传递的,即对于P中的所有a,b和c,我们有:...... ..(一世) . 如果a <b则¬(b <a)(不对称); ........(II) . 如果a <b且b <c则a <c(传递性) . 2:未经测试的评估的执行可能重叠 . 3:不确定顺序的评估不能重叠,但可以先执行 .
C 11中“评价”一词的含义是什么?
在C 11中,表达式(或子表达式)的评估通常包括:
value computations (包括确定glvalue evaluation的对象的标识并获取先前分配给prvalue evaluation的对象的值)和
side effects 的启动 .
现在(§1.9/ 14)说:
int x;
x = 10;
++x;
与
++x
相关的值计算和副作用在x = 10;
的值计算和副作用后排序因此,未定义的行为与上述事物之间必然存在某种关系,对吧?
Yes! 对 .
在(§1.9/ 15)中已经提到过
例如 :
对
+
运算符的操作数的评估相对于彼此未进行排序 .对
<<
和>>
运算符的操作数的评估相对于彼此没有排序 .4:在程序执行期间不止一次评估的表达式中,不需要在不同的评估中一致地执行其子表达式的未序列和不确定顺序的评估 .
这意味着在
x + y
中,x
和y
的值计算在(x + y)
的值计算之前被排序 .更重要的是
例子:
i = i++ * ++i; // Undefined Behaviour
i = ++i + i++; // Undefined Behaviour
i = ++i + ++i; // Undefined Behaviour
i = v[i++]; // Undefined Behaviour
i = v[++i]: // Well-defined Behavior
i = i++ + 1; // Undefined Behaviour
i = ++i + 1; // Well-defined Behaviour
++++i; // Well-defined Behaviour
f(i = -1, i = -1); // Undefined Behaviour (see below)
表达式
(5)
,(7)
和(8)
不会调用未定义的行为 . 有关更详细的说明,请查看以下答案 .Multiple preincrement operations on a variable in C++0x
Unsequenced Value Computations
Final Note :
如果您发现帖子中有任何缺陷,请发表评论 . 高级用户(代表> 20000)请不要犹豫,编辑帖子以纠正拼写错误和其他错误 .
C++17 (
N4659
)包含一个提议Refining Expression Evaluation Order for Idiomatic C++,它定义了更严格的表达式评估顺序 .特别是,添加了 following sentence :
它使以前未定义的行为的几个案例有效,包括有问题的行为:
然而,其他几个类似的案例仍会导致未定义的行为 .
在
N4140
:但在
N4659
当然,使用符合C 17的编译器并不一定意味着应该开始编写这样的表达式 .