当前位置: 首页 > 面试题库 >

未定义的行为和顺序点

顾嘉纳
2023-03-14
问题内容

什么是“序列点”?

未定义行为与序列点之间有什么关系?

我经常使用诸如的有趣且令人费解的表情a[++i] = i;来使自己感觉更好。为什么我应该停止使用它们?


问题答案:

C 98和C 03
此答案适用于C 标准的较旧版本。该标准的C 11和C ++ 14版本没有正式包含“序列点”。操作是“先于”或“未排序”或“不确定地排序”。最终效果基本相同,但是术语不同。

免责声明:好的。这个答案有点长。因此阅读时要有耐心。如果您已经知道这些事情,那么再次阅读它们不会使您发疯。

先决条件:C ++标准的基础知识

什么是序列点?
标准说

在执行序列中某些特定的点(称为顺序点)上,以前评估的所有副作用都应完整,并且以后评估的副作用都不应发生。(第1.9 / 7节)

副作用?有什么副作用?
对表达式的求值会产生某些结果,并且如果执行环境的状态另外发生变化,则可以说表达式(对其求值)会产生一些副作用。

例如:

int x = y++; //where y is also an int

除了初始化操作外,y由于++运算符的副作用,get的值也会更改。

到现在为止还挺好。继续到序列点。comp.lang.c作者提供的seq点的替代定义Steve Summit:

顺序点是粉尘沉淀的时间点,可以保证到目前为止已经看到的所有副作用都是完整的。

C ++标准中列出了哪些通用序列点?
那些是:

在完整表达式(§1.9/16)评估结束时(完整表达式是一个表达式,而不是另一个表达式的子表达式。)1

范例:

int a = 5; // ; is a sequence point here

在对第一个表达式(§1.9/18)2求值之后对以下每个表达式的求值

a && b (§5.14)
a || b (§5.15)
a ? b : c (§5.16)
a , b (§5.18)(这里a,b是逗号运算符; infunc(a,a ) ,不是逗号运算符,它只是参数a和之间的分隔符a 。因此,在这种情况下,行为是不确定的(如果a认为是原始类型))
在对所有函数参数(如果有)进行求值之后(在函数主体中执行任何表达式或语句之前),在函数调用时(函数是否为内联§1.9/17)。

1:注意:对完整表达式的评估可以包括对不属于完整表达式的词汇的子表达式的评估。例如,评估默认参数表达式(8.3.6)所涉及的子表达式被认为是在调用函数的表达式中创建的,而不是在定义默认参数的表达式中创建的

2:所指示的运算符是内置运算符,如第5节中所述。当这些运算符之一在有效上下文中被重载(第13节),从而指定了用户定义的运算符函数时,该表达式指定函数调用,并且操作数形成一个参数列表,它们之间没有隐含的序列点。

什么是未定义行为?
该标准在本节§1.3.12中将未定义行为定义为

行为,例如在使用错误的程序构造或错误的数据时可能发生的行为,对此本国际标准不施加任何要求3。

当本国际标准省略对行为的任何明确定义的描述时,也可能会出现未定义的行为。

3:允许的不确定行为,范围从完全忽略情况以无法预测的结果,到在翻译或程序执行期间以环境特征的书面方式记录的行为(有无诊断消息),到终止翻译或执行(发出诊断消息)。

简而言之,未定义的行为意味着从守护程序从鼻子飞出到女友怀孕都可能发生任何事情。

未定义行为和序列点之间有什么关系?
在开始讨论之前,您必须了解“未定义行为”,“未指定行为”和“实现已定义行为”之间的区别。

您还必须知道这一点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。

例如:

int x = 5, y = 6;

int z = x++ + y++; //it is unspecified whether x++ or y++ will be evaluated first.

这里的另一个例子。

现在标准中§5/4说

1)在上一个序列点与下一个序列点之间,标量对象最多应通过表达式的计算修改其存储值。
这是什么意思?

非正式地,它意味着两个序列点之间的变量不得被多次修改。在表达式语句中,next sequence point通常位于终止分号,而通常位于前一条语句previous sequence point的末尾。表达式也可以包含intermediate sequence points。

从上面的句子中,以下表达式调用未定义的行为:

i++ * ++i;   // UB, i is modified more than once btw two SPs
i = ++i;     // UB, same as above
++i = 2;     // UB, same as above
i = ++i + 1; // UB, same as above
++++++i;     // UB, parsed as (++(++(++i)))

i = (i, ++i, ++i); // UB, there's no SP between `++i` (right most) and assignment to `i` (`i` is modified more than once btw two SPs)

但是以下表达式很好:

i = (i, ++i, 1) + 1; // well defined (AFAIK)
i = (++i, i++, i);   // well defined 
int j = i;
j = (++i, i++, j*i); // well defined

2)此外,应仅访问先验值以确定要存储的值。
这是什么意思?这意味着,如果将对象写入完整表达式内,则在同一表达式内对该对象的任何和所有访问都必须直接参与要写入的值的计算。

例如,在i = i + 1所有访问中i(在LHS和RHS中)直接涉及要写入的值的计算。这样很好

该规则有效地限制了法律表达方式,使其具有明显可在修改之前进行的访问权限。

范例1:

std::printf("%d %d", i,++i); // invokes Undefined Behaviour because of Rule no 2

范例2:

a[i] = i++ // or a[++i] = i or a[i++] = ++i etc

之所以被禁止,是因为i(的in的a[i])访问之一与最终存储在i中的值(发生在i++)没有任何关系,因此没有很好的定义方式-无论是出于我们的理解还是编译器的访问是否应该在存储增量值之前或之后进行。因此,行为是不确定的。

例子3:

int x = i + i++ ;// Similar to above


 类似资料:
  • 关于SO的两个主题解释了C++11上下文中的这些示例。这里说调用UB,定义良好。这里说这两个例子都没有定义。这种模棱两可让我很困惑。这篇结构良好的参考文献我已经读了三遍了,但是这个主题对我来说似乎太复杂了。 . 让我们分析示例:。 相应的报价有: > 内置的预增量和预减量运算符的副作用在其值计算之前被排序(由于定义为复合赋值而导致的隐式规则) 内建赋值运算符和所有内建复合赋值运算符的副作用(左参数

  • 尽管标题出现了,但这并不是一个哲学问题。 从未初始化的数组读取 使用错误数据 使用不可移植构造。(即内存分配的细节1) 导致具有的行为 标准没有要求产生可预测的效果 我会称之为“未定义的行为”。但也许我错过了什么(?) null null

  • 今天我遇到了一些在clang(3.7-git)、g(4.9.2)和Visual Studio 2013上表现出不同行为的代码。经过一些简化,我想出了这个突出问题的片段: g(4.9.2)给出了以下输出: 所以g将参数计算到缓冲区,然后缓冲区(...)本身,然后将索引参数计算到数组运算符。直观地说,这对我来说是有意义的。 clang(3.7-git)和Visual Studio 2013都提供: 我

  • 问题内容: 我有一张带有类别的表: 现在,我想将select语句的结果排序为 例如。在MySQL中,您可以这样做: 但是,您将如何在SQLite中做到这一点?我没有“订购依据”字段。 问题答案:

  • 在信号处理程序中使用sem_post()是否依赖于未定义的行为? 如果信号处理程序引用除errno以外的任何具有静态存储持续时间的对象,而不是通过向声明为volatile sig_atomic_t的对象赋值,或者如果信号处理程序调用本标准中定义的任何函数,而不是[显式异步信号安全函数]之一,则行为未定义。

  • 问题内容: 我正在使用ElasticSearch 2.4.2(通过Java的HibernateSearch 5.7.1.Final)。 我对字符串排序有问题。我的应用程序的语言带有变音符号,它们具有特定的字母顺序。例如,直接在after之后,在after之后,等等。因此,您应该对字符串进行如下排序: ElasticSearch首先按典型字母排序,然后将所有奇怪的字母移到最后: 我可以为Elasti