C和C ++与序列点和UB的差异

时间:2014-07-22 19:54:35

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

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

*当然我不是在谈论C++不适用于C的功能。

1 个答案:

答案 0 :(得分:4)

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

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

序列点

由于我们正在谈论sequence points,所以这涵盖了预C ++ 11和前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)之后调用函数。
  • 以下运算符的第一个操作数的结尾:逻辑AND&& (6.5.13); 逻辑OR || (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);返回语句中的表达式 (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中的预增量为左值,但在C11中则不然。

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

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