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

int f() { static int i=0; return ++i; } int g() { return f() + f(); } 

g()返回3还是undefined的结果?

章和节 :

6.5.2.2函数调用

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


94)换句话说,函数执行不会相互“交错”

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

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

对+运算符的两个操作数的评估是未排序的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行事。

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

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

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

 return f() - f(); 

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

未定义的行为没有理由。 静态变量将存储在.BSS内存空间中,并且不会创建它的副本 – 编译器将处理此问题。 f()函数将按顺序调用两次。 以下案例类似:

 for (int i = 0; i < 2; ++i) g += f(); 

如果f()函数被调用为两个线程,那将导致一些未定义的行为。

以下是C11第6.5节第2段的引用:

如果对标量对象的副作用相对于对同一标量对象的不同副作用或使用相同标量对象的值进行的值计算未进行排序,则行为未定义。 如果表达式的子表达式有多个允许的排序,则如果在任何排序中出现这种未测序的副作用,则行为是不确定的.84

但是根据第5.1.2.3节第3段,排序是不确定的,而不是没有排序,所以上面的第一句不适用。 第二句仅适用于任何排序中存在未测序的副作用。 但在每个排序中都没有无序的副作用。

C11标准似乎没有提到不确定序列计算的影响。 也许我们要得出结论,符合标准的编译器可以产生任何可能序列的输出。 在这种解释中,有两个可能的序列,这两个序列都导致g()返回值3。

所以我认为这是可以接受的。

请注意,在C99中,没有与C11中第5.1.2.3节相对应的部分。 第5.5节第3段说,子表达式的排序和副作用发生的顺序都是未指定的。

“未指定”与“未定义”不同。

这让我相信在C99中这不是未定义的行为。