以未指定的顺序使用具有副作用的函数是否是未定义的行为?

klu*_*utt 19 c undefined-behavior

我知道诸如x = x++ + ++x调用未定义行为之类的事情,因为变量在同一序列点内被多次修改。这在这篇文章中进行了彻底的解释为什么这些构造使用前增量和后增量未定义行为?

但是考虑像printf("foo") + printf("bar"). 该函数printf返回一个int,因此该表达式在这个意义上是有效的。但是+标准中没有规定运算符的求值顺序,所以不清楚这会打印foobar还是barfoo

但我的问题是这是否也是未定义的行为。

Eri*_*hil 14

printf("foo") + printf("bar") 没有未定义的行为(下面提到的警告除外),因为函数调用是不确定的,并且不是无序的。

C实际上具有三种排序可能性:

  • 两个事物,A 和 B,可以按特定顺序排列,其中一个是 A 在 B 之前或 B 在 A 之前。
  • 两个事物的顺序可能不确定,因此 A 在 B 之前排序,反之亦然,但未指定哪个。
  • 有两件事是无序的。

为了区分后两者,假设写入stdout需要将字节放入缓冲区并更新缓冲区中有多少字节的计数器。(为此,我们将忽略缓冲区已满或应该发送到输出设备时发生的情况。)考虑对 的两次写入stdout,称为 A 和 B。

如果 A 和 B 的顺序不确定,那么任何一个都可以先进行,但它的两个部分——写入字节和更新计数器——必须在另一个开始之前完成。如果 A 和 B 是未排序的,则没有什么可以控制这些部分;我们可能有:A 将其字节放入缓冲区,B 将其字节放入缓冲区,A 更新计数器,B 更新计数器。

在前一种情况下,两个写入都已完成,但它们可以按任一顺序完成。在后一种情况下,行为未定义。一种可能性是 B 将其字节写入缓冲区中与 A 字节相同的位置,从而丢失了 A 的字节,因为计数器未更新以告诉 B 其新字节应该去哪里。

在 中printf("foo") + printf("bar"),写入的stdout顺序不确定。这是因为函数调用提供了分隔副作用的序列点,但我们不知道它们的计算顺序。

C 2018 6.5.2.2 10 告诉我们函数调用引入序列点:

在函数指示符和实际参数的计算之后但在实际调用之前有一个序列点。调用函数(包括其他函数调用)中的每一个在被调用函数体执行之前或之后没有特别排序的评估都相对于被调用函数的执行是不确定的。

因此,如果 C 实现恰好在printf("foo")第二个评估,则在实际调用之前有一个序列点,并且 的评估printf("bar")必须在此之前被排序。相反,如果实现printf("bar")首先评估,则printf("foo")必须在它之前进行排序。因此,尽管不确定,但还是有顺序的。

此外,7.1.4 3 告诉我们:

在库函数返回之前有一个序列点。

因此,这两个函数调用的顺序是不确定的。6.5 2 中关于未排序副作用的规则不适用:

如果对标量对象的副作用相对于对同一标量对象的不同副作用或使用同一标量对象的值进行的值计算而言是未排序的,则行为是未定义的……

(更不用说它stdout不是标量对象的事实。)

警告

C 标准允许将标准库函数实现为类似函数的宏 (C 2018 7.1.4 1) 存在危险。在这种情况下,上面关于序列点的推理可能不适用。程序可以通过封闭括号中的名称,以便它不会作为一个函数宏的调用来处理强制函数调用:(printf)("foo") + (printf)("bar")

  • 这似乎是一个不错的答案,但说实话,我不确定您是否声称这是未定义的行为。 (2认同)
  • @Teepeemm:“未定义行为”是 C 标准中的一个特定术语,意味着该标准在特定情况下没有强加任何要求。如果 X 或 Y 可以发生,但其中之一必须发生,那么这不是未定义的行为,因为标准强加了一些要求,即使它没有完全指定必须发生的情况。这是“未指定的行为”,其中标准允许两种或多种可能性,并且在任何情况下都不会强加选择的要求。 (2认同)

P__*_*J__ 9

不它不是。

这是未指定的行为

在此处输入图片说明

  • 我认为如果您更改文本和链接的图片,这个答案会受益匪浅 (9认同)
  • 顺序肯定是未指定的,但我想知道写入 stdout 的事实是否不属于“未定义”类别,因为“2)如果标量对象的副作用相对于使用相同标量对象的值,行为未定义。` https://en.cppreference.com/w/cpp/language/eval_order 需要明确的是:我不确定,因此我要问。 (3认同)
  • @alagner:对“stdout”的写入是不确定顺序的(它们必须以某种顺序发生,写入彼此分开,尽管未指定顺序),而不是无序的(不会阻止写入重叠,其中一部分相对于其他部分而言可能会发生混乱)。 (2认同)