c - 以不确定的顺序使用具有副作用的函数是否存在不确定的行为?

标签 c undefined-behavior

我知道像x = x++ + ++x这样的事情会调用未定义的行为,因为变量在同一序列点内被多次修改。 Why are these constructs using pre and post-increment undefined behavior?对此进行了详细说明
但是考虑像printf("foo") + printf("bar")这样的事情。函数printf返回一个整数,因此该表达式在这种意义上是有效的。但是在标准中未指定+运算符的求值顺序,因此尚不清楚它将打印foobar还是barfoo
但是我的问题是这是否也是未定义的行为。

最佳答案

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告诉我们函数调用引入了序列点:

    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")进行排序。因此,尽管不确定,但存在排序。
    另外,7.1.4 3告诉我们:

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


    因此,两个函数调用的顺序不确定。 6.5 2中有关无序副作用的规则不适用:

    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不是标量对象的事实。)
    警告
    C标准允许将标准库函数实现为类似函数的宏(C 2018 7.1.4 1),这是一个危险。在这种情况下,以上关于序列点的推理可能不适用。程序可以通过将名称括在圆括号中来强制执行函数调用,这样它就不会被视为对类似函数的宏(printf)("foo") + (printf)("bar")的调用。

    关于c - 以不确定的顺序使用具有副作用的函数是否存在不确定的行为?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/63656184/

    相关文章:

    c - 在c中实现快速排序

    CC2650编程通过单击按钮执行不同的语句

    c - SIGCHLD 导致段错误,未进入处理程序

    c++ - 元组的一个元素可以引用另一个元素吗?

    c - if((result=f())==0) 是未定义的行为吗?

    将 T[][] 转换为 T*

    rust - ManuallyDrop<Box<T>> 是否具有 mem::uninitialized 定义的行为?

    c++ - C++03 12.4/12 中关于通过指针显式调用基类析构函数的说法是什么?

    c - 数组索引的散列

    c - 从C中的文件读取空格分隔的值时出现段错误