在相同的表达式未定义行为中调用具有局部副作用的函数两次?

l4m*_*4m2 24 c undefined-behavior

int f() {
    static int i=0;
    return ++i;
}
int g() {
    return f() + f();
}
Run Code Online (Sandbox Code Playgroud)

是否g()返回3或者是结果undefined

Joh*_*ode 18

章和节:

6.5.2.2函数调用
...
10在评估函数指示符和实际参数之后但在实际调用之前有一个序列点.调用函数(包括其他函数调用)中的每个评估(在执行被调用函数的主体之前或之后没有特别排序)对于被调用函数的执行是不确定地排序的.94)
94)换句话说,函数执行不会相互"交错"

Upshot是因为++i它是函数调用的一部分而在每个之间存在一个序列点.因此,这种行为是明确定义的.

它是否真的符合您的意图是另一回事.请注意,在某些时候,您冒着签名溢出的风险,这是未定义的.正如其他人所指出的那样,f() - f()可能无法给出您期望的结果(在这种情况下,不能保证从左到右的评估).


250*_*501 15

+运算符的两个操作数的评估未被排序1.

在实际函数调用2之前有一个序列点.这个序列点足以分离静态变量i的修改,使整个表达式不确定地排序,并且函数的顺序调用未指定3.

因此行为保持定义,并且对函数g的第一次调用将总是产生3,因为函数调用的未指定顺序不会影响结果.

定义了包含未指定行为的程序4.


(所有引用来自:ISO/IEC 9899:201x)

1(6.5表达式3)
除非后面指出,否则子表达的副作用和值计算未被排序.

2(6.5.2.2函数调用10)
在评估函数指示符和实际参数之后但在实际调用之前有一个序列点.

3 (5.1.2.3程序执行3)
当A在B之前或之后进行测序时,评估A和B是不确定的,但未指定哪一个.

4(4.一致性3)
在所有其他方面正确的程序,对正确数据进行操作,包含未指明的行为,应为正确的程序,并按照5.1.2.3的规定行事.


das*_*ght 11

没有理由将其定义为未定义,因为+操作是可交换的,并且因为有两个++操作的序列点要排序.

C标准在完整表达式之后以及在函数调用中输入函数之前具有序列点.因此,结果++将完全排序.而且,由于+是可交换的,所以调用的顺序f()不会改变结果.

请注意,相同的逻辑不适用于

return f() - f();
Run Code Online (Sandbox Code Playgroud)

因为-不是交换.上面表达式的结果是未指定的,即符合标准的编译器可以合理地生成a 1或a -1,具体取决于编译器调用这两个f()函数的顺序.

  • @ l4m2:未定义的行为和未指定的行为方面是完全不同的概念.未定义的行为意味着`int i = 0; printf("%d",i ++ - i ++);`不仅可以输出-1,输出0或输出1,还可以输出"Fred"或否定时间和因果关系的定律.相比之下,`f() - f()`将输出1或输出-1 - 它不能做任何其他事情. (6认同)
  • @ l4m2"未定义","未指定","实现定义","不确定值","不确定序列","未序列"以及一些其他相关术语在C标准中具有精确定义,并且不可互换. (5认同)
  • 结果不可能是不确定的.表达式是不确定的,并且结果未指定,无论是1还是-1. (3认同)
  • `f() - f()`仍然不是UB,只是实现定义.也就是说,你的解释部分是关于序列点的,仍然适用于`f() - f()`. (2认同)
  • @ sepp2k:未定义实现,但不确定.实现定义意味着实现必须指定行为并保证它.但是在这里,功能的评估顺序可以在同一程序中变化. (2认同)