在C99中,f()+ g()未定义或仅仅未指定?

Pas*_*uoq 54 c c99 undefined-behavior sequence-points unspecified-behavior

我以前认为在C99中,即使函数的副作用fg干扰,虽然表达式f() + g()不包含序列点,f并且g会包含一些,所以行为将是未指定的:要么f()之前调用f()之前的g()或g().

我不再那么肯定了.如果编译器内联函数(即使未声明函数,编译器可能决定这样做inline)然后重新排序指令,该怎么办?可能有人得到上述两种不同的结果吗?换句话说,这是未定义的行为吗?

这不是因为我打算写这种东西,而是在静态分析器中为这样的语句选择最佳标签.

Jon*_*ler 25

表达式f() + g()包含至少4个序列点; 调用之前的一个f()(在评估其所有零参数之后); 调用之前的一个g()(在评估其所有零参数之后); 一个作为f()回报的召唤; 和一个作为g()回报的召唤.此外,与之相关的两个序列点在与之关联的两个序列点f()之前或之后都发生g().您无法分辨的是序列点将发生在哪个顺序 - f点是否出现在g点之前,反之亦然.

即使编译器内联代码,它也必须遵守"似乎"规则 - 代码必须与函数不交错时的行为相同.这限制了损坏的范围(假设没有错误的编译器).

因此未指定其中f()g()评估的顺序.但其他一切都很干净.


在评论中,supercat问:

我希望源代码中的函数调用仍然作为序列点,即使编译器自己决定内联它们也是如此.声明为"内联"的函数是否仍然存在,或者编译器是否获得额外的自由度?

我相信'似乎'规则适用,并且编译器没有额外的宽容度来省略序列点,因为它使用显式inline函数.认为(懒得去寻找标准中的确切措辞)的主要原因是允许编译器根据其规则内联或不内联函数,但程序的行为不应该改变(除了性能).

另外,关于排序的可以说什么(a(),b()) + (c(),d())?是否有可能为c()和/或d()之间的执行a()b(),或a()b()之间的执行c()d()

  • 显然,a在b之前执行,c在d之前执行.我相信c和d有可能在a和b之间执行,尽管编译器生成类似代码的可能性很小; 类似地,a和b可以在c和d之间执行.虽然我在'c和d'中使用'和',但这可能是'或' - 也就是说,这些操作序列中的任何一个都满足约束条件:

    • 绝对允许
    • A B C D
    • CDAB
    • 可能允许(保留≺b,c≺d订购)
    • ACBD
    • ACDB
    • 农发行
    • CABD

     
    我认为这涵盖了所有可能的序列.另请参阅Jonathan Leffler和AnArrayOfFunctions之间聊天 - 要点是AnArrayOfFunctions认为根本不允许"可能允许"的序列.

如果这样的事情是可能的,那就意味着内联函数和宏之间存在显着差异.

内联函数和宏之间存在显着差异,但我不认为表达式中的顺序是其中之一.也就是说,可以用宏替换函数a,b,c或d中的任何函数,并且可以发生宏体的相同排序.在我看来,主要区别在于使用内联函数,在函数调用中有保证的序列点 - 如主要答案中所述 - 以及逗号运算符.使用宏,您将丢失与功能相关的序列点.(所以,也许这是一个显着的差异......)然而,在很多方面,这个问题就像是有多少天使可以在别针头上跳舞的问题 - 这在实践中并不是很重要.如果有人向我提出了表达方式(a(),b()) + (c(),d()) 在代码审查中,我会告诉他们重写代码以使其清楚:

a();
c();
x = b() + d();
Run Code Online (Sandbox Code Playgroud)

这假设对b()vs 没有关键的排序要求d().


R..*_*R.. 14

有关序列点列表,请参见附录C. 函数调用(被评估的所有参数和传递给函数的执行之间的点)是序列点.正如您所说,未指定哪个函数首先被调用,但是这两个函数中的每一个都将看到另一个函数的所有副作用,或者根本没有.