当前位置: 首页 > 知识库问答 >
问题:

序列点模糊,未定义的行为?

秦渝
2023-03-14

今天我遇到了一些在clang(3.7-git)、g(4.9.2)和Visual Studio 2013上表现出不同行为的代码。经过一些简化,我想出了这个突出问题的片段:

#include <iostream>
using namespace std;

int len_ = -1;

char *buffer(int size_)
{
    cout << "len_: " << len_ << endl;
    return new char[size_];
}

int main(int argc, char *argv[])
{
    int len = 10;
    buffer(len+1)[len_ = len] = '\0';
    cout << "len_: " << len_ << endl;
}

g(4.9.2)给出了以下输出:

len_: -1
len_: 10

所以g将参数计算到缓冲区,然后缓冲区(...)本身,然后将索引参数计算到数组运算符。直观地说,这对我来说是有意义的。

clang(3.7-git)和Visual Studio 2013都提供:

len_: 10
len_: 10

我想clang和VS2013在进入缓冲区(...)之前评估了一切可能的情况。这对我来说没有那么直观的意义。

我想我的问题的要点是这是否是一个明确的未定义行为的案例。

编辑:感谢您澄清这一点,未指定的行为是我应该使用的术语。

共有2个答案

酆俊远
2023-03-14

嗯,不,这不是一个未定义行为的情况。这是一个未指明行为的情况。

表达式 len_ = len 是在 buffer(len 1) 之前还是之后计算的,这是未指定的。根据您描述的输出,g 首先计算 buffer(len 1),clang 首先计算 len_ = len

两种可能性都是正确的,因为这两个子表达式的求值顺序是不确定的。两个表达式都将被求值(因此行为不符合未定义的条件),但是标准没有指定顺序。

叶建柏
2023-03-14

这是未指定的行为,len_=len相对于缓冲区()主体的执行是不确定的顺序,这意味着一个将在另一个之前执行,但它没有指定哪个顺序,但有一个顺序,因此评估不能重叠,因此没有未定义的行为。这意味着gccclangVisual Studio都是正确的。另一方面,未排序的评估允许重叠的评估,这可能导致未定义的行为,如下所述。

摘自C 11标准草案第1.9节[简介执行]:

[...]调用函数(包括其他函数调用)中的每个求值,如果在被调用函数体执行之前或之后没有特别排序,则相对于被调用函数的执行是不确定排序的...]

《不确定序列》在这之前有所涉及,它说:

[...]当A排在B之前或B排在A之前时,A和B的排序是不确定的,但未指明是哪一个。[注意:不确定顺序的评估不能重叠,但可以先执行其中任何一个。—结束注释]

这与未排序的评估不同:

[...]如果A不在B and B之前排序,那么A和B是不排序的。[注意:未排序评估的执行可能会重叠。—结束注释][...]

这可能导致未定义的行为(强调我的):

除非另有说明,否则单个运算符的操作数和单个表达式的子表达式的计算是无序的。[ 注意:在程序执行期间多次计算的表达式中,不需要在不同的计算中一致地执行对其子表达式的无序和不确定排序的计算。运算符的操作数的值计算在运算符结果的值计算之前进行排序。如果标量对象上的副作用相对于同一标量对象上的另一个副作用或使用同一标量对象的值计算未排序,则该行为未定义[...]

预科 C 11

在C 11之前,子表达式的计算顺序也是未指定的,但它使用序列点而不是排序。在这种情况下,在函数入口和函数出口有一个序列点,可确保没有未定义的行为。从第1.9节:

[…]函数入口和函数出口处的序列点(如上所述)是所评估的函数调用的特征,无论调用函数的表达式的语法是什么。

确定评估顺序

根据您的观点和期望,每个编译器做出的不同选择可能看起来不直观。确定评估顺序的主题是EWG第158期的主题:N4228改进惯用C的表达式评估顺序,这正在考虑用于C 17,但根据对该主题的投票反应,似乎有争议。本文涵盖了“C编程语言”第四版中一个更复杂的案例。这表明即使是那些有丰富C语言经验的人也可能出错。

 类似资料:
  • 问题内容: 什么是“序列点”? 未定义行为与序列点之间有什么关系? 我经常使用诸如的有趣且令人费解的表情a[++i] = i;来使自己感觉更好。为什么我应该停止使用它们? 问题答案: C 98和C 03 此答案适用于C 标准的较旧版本。该标准的C 11和C ++ 14版本没有正式包含“序列点”。操作是“先于”或“未排序”或“不确定地排序”。最终效果基本相同,但是术语不同。 免责声明:好的。这个答案

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

  • 问题内容: 从节点手册中,我可以使用来获得文件的目录,但是从REPL中,这似乎是未定义的。这是我的误解还是错误在哪里? 问题答案: 仅在脚本中定义。在REPL中不可用。 尝试制作脚本 并运行它: 您将看到打印。 添加了背景说明:表示“此脚本的目录”。在REPL中,您没有脚本。因此,将没有任何实际意义。

  • 我试图定义一些endpoint,并使用进行测试。在中,我有: 但是当我运行时,我会得到以下错误: 我如何解决这个问题?

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

  • 我收到此意外错误nodejs(Express)。每当我需要控制台时。文件或请求。文件,未定义为控制台 三个文件如下: 用于注册 处理所有multer配置 所有文件的父级