【问题标题】:Differences in C and C++ with sequence points and UBC 和 C++ 与序列点和 UB 的区别
【发布时间】:2014-07-22 19:54:35
【问题描述】:

我使用这篇文章Undefined Behavior and Sequence Points 来记录 C 程序中未定义的行为(UB),并指出C and C++ have their own divergent rules for this [sequence points]。那么CC++在序列点和相关UB上有什么区别呢?我不能用一篇关于 C++ 序列的帖子来分析 C 代码中发生的事情吗?

* 当然我不是在说C++ 的功能不适用于C

【问题讨论】:

标签: c++ c language-lawyer undefined-behavior sequence-points


【解决方案1】:

这个问题有两个部分,我们可以毫不费力地解决序列点规则的比较。这并没有让我们走得太远,C 和 C++ 是具有不同标准的不同语言(latest C++ standard 几乎是 latest C standard 的两倍),即使 C++ 使用 C 作为规范性参考引用 C 的 C++ 标准是不正确的,反之亦然,无论某些部分可能多么相似。 C++ 标准确实明确引用了 C 标准,但这是针对小部分的。

第二部分是undefined behavior 在 C 和 C++ 之间的比较,可能存在一些很大的差异,可能无法列举所有未定义行为的差异,但我们可以给出一些指示性示例。

序列点

既然我们谈论的是sequence points,那么这涵盖了 pre C++11 和 pre C11。据我所知,C99 和 Pre C++11 草案标准之间的序列点规则差别不大。正如我们将在我给出的不同未定义行为的一些示例中看到的那样,序列点规则在其中不起作用。

序列点规则包含在closest draft C++ standard to C++03 部分1.9 程序执行 中说:

  • 每个完整表达式的评估完成时都有一个序列点12)
  • 调用函数时(无论函数是否内联),所有求值后都有一个序列点 在执行函数体中的任何表达式或语句之前发生的函数参数(如果有)。
  • 在复制返回值之后和执行外部任何表达式之前还有一个序列点 函数13)。 C++ 中的几个上下文会导致对函数调用进行评估,即使没有相应的函数调用 语法出现在翻译单元中。 [ 示例:新表达式的评估调用一个或多个分配和 构造函数;见 5.3.4。再举一个例子,转换函数(12.3.2)的调用可能出现在上下文中 其中没有出现函数调用语法。 --end example ] 序列指向函数入口和函数出口 (如上所述)是评估后的函数调用的特征,无论调用 功能可能是。
  • 在每个表达式的评估中

    a && b
    a || b
    a ? b : c
    a , b
    

    利用这些表达式(5.14、5.15、5.16、5.18)中运算符的内置含义,后面有一个序列点 第一个表达式的求值14)。

我将使用 C99 标准草案Annex C 中的序列点列表,虽然它不是规范的,但我找不到与它所引用的规范部分的不同意见。它说:

以下是5.1.2.3中描述的顺序点:

  • 在计算参数后调用函数 (6.5.2.2)。
  • 以下运算符的第一个操作数的结尾:逻辑与 && (6.5.13); 逻辑或 || (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); return 语句中的表达式 (6.8.6.4)。

以下条目似乎在 C++ 标准草案中没有等效项,但它们来自 C++ 通过引用合并的 C 标准库:

  • 就在库函数返回之前 (7.1.4)。
  • 在与每个格式化输入/输出函数转换相关的动作之后 说明符(7.19.6、7.24.2)。
  • 在每次调用比较函数之前和之后,以及 也在对比较函数的任何调用和对象的任何移动之间 作为参数传递给该调用 (7.20.5)。

所以这里的 C 和 C++ 没有太大区别。

未定义行为

当涉及到序列点和未定义行为的典型示例时,例如在第 5表达式 部分中涉及的处理在序列点内多次修改变量的那些我不能来举一个在一个中未定义但在另一个中未定义的示例。在 C99 中它说:

在前一个序列点和下一个序列点之间,一个对象应该有它的 存储的值最多修改一次由评估 表达式。72) 此外,先验值应只读到 确定要存储的值。73)

它提供了这些例子:

i = ++i + 1;
a[i++] = i;

在 C++ 中它说:

除非另有说明,个别操作数的求值顺序 单个表达式的运算符和子表达式,以及顺序 副作用发生的位置,未指定。57) 前一个和下一个序列点一个标量对象应该有它的存储 表达式的评估最多修改一次的值。 此外,应仅访问先验值以确定 要存储的值。应满足本款的要求 对于完整的子表达式的每个允许排序 表达;否则行为未定义

并提供以下示例:

i = v[i ++]; / / the behavior is undefined
i = ++ i + 1; / / the behavior is undefined

在 C++11 和 C11 中,我们确实有一个主要区别,Assignment operator sequencing in C11 expressions 中介绍了以下内容:

i = ++i + 1;

这是由于预增量在 C++11 中是 lvalue 的结果,但在 C11 中不是,即使排序规则相同。

我们在与序列点无关的领域确实存在重大差异:

可能还有更多示例,但这些是我之前写过的。

【讨论】:

    猜你喜欢
    • 2013-05-19
    • 1970-01-01
    • 1970-01-01
    • 2011-05-11
    • 1970-01-01
    • 2017-04-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多