以未指定的顺序使用具有副作用的函数是否是未定义的行为? [英] Is it undefined behavior to use functions with side effects in an unspecified order?

查看:24
本文介绍了以未指定的顺序使用具有副作用的函数是否是未定义的行为?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I know that things like x = x++ + ++x invokes undefined behavior because a variable is modified multiple times within the same sequence point. That's thoroughly explained in this post Why are these constructs using pre and post-increment undefined behavior?

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

But consider a thing like printf("foo") + printf("bar"). The function printf returns an int, so the expression is valid in that sense. But the order of evaluation for the + operator is not specified in the standard, so it is not clear if this will print foobar or barfoo.

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

But my question here is if this also is undefined behavior.

推荐答案

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

printf("foo") + printf("bar") does not have undefined behavior (except for the caveat noted below) because the function calls are indeterminately sequenced and are not unsequenced.

C 有效地具有三种排序可能性:

C effectively has three possibilities for sequencing:

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

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

To distinguish between the latter two, suppose writing to stdout requires putting bytes in a buffer and updating the counter of how many bytes are in the buffer. (For this, we will neglect what happens when the buffer is full or should be sent to the output device.) Consider two writes to stdout, called A and B.

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

If A and B are indeterminately sequenced, then either one can go first, but both of its parts—writing the bytes and updating the counter—must be completed before the other one starts. If A and B are unsequenced, then nothing controls the parts; we might have: A puts its bytes in the buffer, B puts its bytes in the buffer, A updates the counter, B updates the counter.

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

In the former case, both writes are completed, but they can be completed in either order. In the latter case, the behavior is undefined. One of the possibilities is that B writes its bytes in the same place in the buffer as A’s bytes, losing A's bytes, because the counter was not updated to tell B where its new bytes should go.

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

In printf("foo") + printf("bar"), the writes to stdout are indeterminately sequenced. This is because the function calls provide sequence points that separate the side effects, but we do not know in which order they are evaluated.

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

C 2018 6.5.2.2 10 tells us that function calls introduce sequence points:

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

There is a sequence point after the evaluations of the function designator and the actual arguments but before the actual call. Every evaluation in the calling function (including other function calls) that is not otherwise specifically sequenced before or after the execution of the body of the called function is indeterminately sequenced with respect to the execution of the called function.

因此,如果 C 实现恰好在第二个评估 printf("foo"),那么在实际调用之前会有一个序列点,并且 printf("bar") 必须在此之前排序.相反,如果实现首先计算 printf(bar"),则 printf(foo") 必须在它之前被排序.所以,有顺序,尽管不确定.

Thus, if the C implementation happens to evaluate printf("foo") second, there is a sequence point just before the actual call, and the evaluation of printf("bar") must have been sequenced before this. Conversely, if the implementation evaluates printf("bar") first, then printf("foo") must have been sequenced before it. So, there is sequencing, albeit indeterminate.

另外,7.1.4 3 告诉我们:

Additionally, 7.1.4 3 tells us:

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

There is a sequence point immediately before a library function returns.

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

Therefore, the two function calls are indeterminately sequenced. The rule in 6.5 2 about unsequenced side effects does not apply:

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

If a side effect on a scalar object is unsequenced relative to either a different side effect on the same scalar object or a value computation using the value of the same scalar object, the behavior is undefined…

(更不用说stdout 不是标量对象这一事实.)

(Not to mention the fact that stdout is not a scalar object.)

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

There is a hazard that the C standard permits standard library functions to be implemented as function-like macros (C 2018 7.1.4 1). In this case, the reasoning above about sequence points might not apply. A program can force function calls by enclosing the name in parentheses so that it will not be treated as an invocation of a function-like macro: (printf)("foo") + (printf)("bar").

这篇关于以未指定的顺序使用具有副作用的函数是否是未定义的行为?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆