什么是「;序列点“
未定义的行为和序列点之间的关系是什么
我经常使用有趣而复杂的表达式,比如a[++I]=I,让自己感觉更好。我为什么要停止使用它们
如果您已经阅读了本文,请务必访问后续问题未定义的行为和重新加载的序列点
(注:这是一个栈溢出的条目,C++的FAQ。如果你想在这个表单中提供一个常见问题的想法,那么在Meta上发布所有这些都是做这个的地方。在C++聊天室中,这个问题的答案被监控,首先是FAQ的想法,所以你的答案很有可能。我想让想出这个主意的人读一读。)
C++98和C++03
这是针对C++标准的旧版本。标准的C++ 11和C++ 14版本没有正式包含“序列点”;操作是“先排序”或“未排序”或“不确定排序”。网络效果本质上是相同的,但术语不同。
免责声明:好的。这个答案有点长。所以在阅读时要有耐心。如果你已经知道这些东西,再次阅读它们不会让你发疯
前必备< >:C++标准
的基础知识什么是序列点
标准上说
在执行序列中称为序列点的特定点,先前评估的所有副作用
应完整且未发生后续评估的副作用。(§1.9/7)
副作用?什么是副作用
表达式的求值会产生一些结果,此外,如果执行环境的状态发生变化,则表示表达式(其求值)有一些副作用
例如:
intx=y++;//其中y也是一个int
除了初始化操作外,y的值由于++运算符的副作用而改变
到目前为止还不错。转到序列点。由comp.lang.c作者Steve Summit给出的序列点的替代定义
序列点是一个时间点,在这个时间点上,尘埃已经沉淀下来,到目前为止已经看到的所有副作用都保证是完全的
H2> C++标准中列出的公共序列点是什么?
这些是:
-
在完整表达式(
§1.9/16)的计算结束时(完整表达式是不是另一个表达式的子表达式的表达式。)1例如:
int a=5;//;是此处的序列点 -
在计算第一个表达式(
§1.9/18)后,计算下列表达式中的每一个表达式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:允许的未定义行为范围从完全忽略具有不可预测结果的情况,到在翻译或程序执行过程中以环境特有的记录方式(带或带)进行行为-
发出诊断消息后)终止转换或执行(发出诊断消息时)。
简而言之,未定义的行为意味着任何事情都可能发生,从你鼻子里飞出的守护进程到你女朋友怀孕
未定义的行为和序列点之间的关系是什么
在我开始之前,您必须了解未定义行为、未指定行为和实现定义行为之间的区别
您还必须知道,未指定单个运算符的操作数和单个表达式的子表达式的求值顺序,以及副作用发生的顺序
例如:
intx=5,y=6;
int z=x+++y++;//未指定是先计算x++还是y++的值。
这里还有一个例子
现在§5/4中的标准是
- 1) 在上一个序列点和下一个序列点之间,通过计算表达式,标量对象的存储值最多修改一次。
这是什么意思
非正式地说,这意味着在两个序列点之间,一个变量不能被修改多次。
在表达式语句中,下一个序列点通常位于终止分号处,而上一个序列点位于上一个语句的末尾。表达式还可以包含中间的序列点
从上面的句子中,以下表达式调用未定义的行为:
i++*++i;//UB,我被修改了不止一次btw两个SP
i=++i;//UB,同上
++i=2;//UB,同上
i=++i+1;//UB,同上
++++++i、 //UB,解析为(+++(+++++i)))
i=(i,++i,++i);//UB,在“++i”(最右边)和分配给“i”之间没有SP(顺便说一句,两个SP对“i”进行了多次修改)
但下面的表达方式很好:
i=(i,++i,1)+1;//定义明确(AFAIK)
i=(++i,i++,i);//明确的
int j=i;
j=(++i,i++,j*i);//明确的
- 2) 此外,只能通过访问先前值来确定要存储的值。
这是什么意思?这意味着,如果在完整表达式中写入对象,则在同一表达式中对该对象的任何和所有访问都必须直接参与待写入值的计算
例如,在i=i+1中,i(在L.H.S和R.H.S中)的所有访问都直接参与要写入的值的计算。所以很好
该规则有效地将合法表达式限制为访问明显先于修改的表达式
例1:
std::printf(“%d%d”,i,++i);//由于第2条规则而调用未定义的行为
例2:
a[i]=i++//或a[++i]=i或a[i++]=i等
是不允许的,因为i的某个访问(在a[i]中的访问)与最终存储在i中的值(发生在i++中)无关,因此,无论是为了我们的理解还是为了编译器的理解,都没有很好的方法来定义访问应该在存储递增值之前还是之后进行。因此,这种行为是未定义的
例3:
intx=i+i++;//同上
在此处跟进C++11的答案。