6

考虑下面的C程序:i = post_increment_i()指定,未指定或未定义的行为?

int i = 0; 

int post_increment_i() { return i++; } 

int main() { 
    i = post_increment_i(); 
    return i; 
} 

关于2011版的C标准(称为C11),下面的替代方案中的哪一个真实的:

  1. C11保证主返回0.
  2. C11保证主返回0或1.
  3. 根据C11,此程序的行为未定义。

从C11标准相关片段:

  • 5.1.2.3程序执行

    访问的易失性对象,修改对象,修改文件或调用一个函数 那这些操作中的任何一个都是副作用,这些副作用是执行环境的状态变化。一般表达的评估包括值计算和副作用的启动。用于左值表达式 的值计算包括确定指定对象的身份。

    之前排序是由单个线程执行的评估 之间的非对称,传递,成对关系,其在这些评估之间诱导部分顺序。 给定任何两个评估A和B,如果A在B之前被排序,那么执行A之前应先执行B.(相反,如果A在B之前被排序,则B在A之后排序为 )如果A在B之前或之后没有被测序,则A和B是未测序的 。当A在B之前或之后被测序为 时,评估A和B被不确定地测序,但未指定哪一个。在对表达式A和B的评估之间存在序列点 意味着在与B相关联的每个值计算和副作用 之前对与A相关联的每个值计算和副作用进行排序。(序列的概述分数见附录C.)

    13)未序贯评估的执行可以交错。不确定排序的评估不能交错,但可以按任何顺序执行。

  • 6.5表达式

    表达式是运营商和操作数的序列,可指定一个 值的计算,或者,指定的对象或功能,或产生副作用,或者说 执行它们的组合。操作符 的操作数的值计算在运算符结果的值计算之前进行排序。

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

  • 6.5.2.2函数调用

    有功能指定者的评估和实际 争论之后,但在实际调用之前的顺序点。在被调用函数主体执行前后,在调用函数(包括其他函数调用)中未进行任何其他特定排序的每个评估均相对于被调用函数的执行被不确定地排序。

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

  • 6.5.2.4后缀增量和减量运算

    后缀的结果++运算符是操作数的值。作为副作用,操作数对象的值 递增(即,将相应类型的值1添加到它的值为 )。 [...]在更新操作数存储值的副作用 之前,对结果的值计算进行排序。关于函数调用的不确定序列 ,postfix ++的操作是单个评估。

  • 6.5.16分配

    赋值运算符存储在由左操作数所指定的对象的值。 [...]更新左操作数的存储值的副作用是在左和右操作数的值计算之后按顺序排列的 。 的操作数的评估是不确定的。

  • 6.8语句和块

    完整表达式是一个不是另一个表达式或一声明符的一部分的表达。 以下每一项都是完整的表达式:[...]表达式语句中的表达式; [...](可选)表达式返回 声明。在完整表达式的评估和下一个要评估的完整表达式的评估之间有一个序列点。

三个替代上述对应于以下三种情况下,分别为:

  1. 后缀增量操作者的副作用在主分配之前被测序。
  2. 后缀增量运算符的副作用在main中的赋值之前或之后进行排序,而C11不指定其中的哪一个。 (换句话说,这两种副作用是不确定的。)
  3. 这两种副作用是不确定的。

看来,第一种选择持有,通过推理的下列链:

  • 考虑规则每个评估调用函数(包括 其他函数调用),是不是另有具体在被调用函数主体的执行之前或之后被执行的被执行的被调用函数的执行被不确定地排序。在6.5.2.2中的。假设A:主要赋值运算符的副作用就是这样的“评估”。假设B:短语“执行被调用函数”包括后缀增量运算符的值计算和后缀增量运算符的副作用。根据这些假设和上述规则,可以得出I)后缀增量运算符的值计算和副作用都在赋值运算符的副作用之前被排序,或者II)值计算和副作用后缀增量运算符的值都是在赋值运算符的副作用后排序的。

  • 考虑规则更新左操作数的存储值的副作用是 ,在左和右操作数的值计算之后进行排序。这条规则排除了上面的情况。因此情况II成立。 QED

总的来说,这看起来像一个非常强大的论据。此外,它对应于人们会直观地考虑最可能的选择。然而,它确实依赖于对术语“评估”和“被调用函数的执行”(假设A和B)的具体解释以及不完全直接的推理线,所以我想把它放在那里看看人们是否有理由相信这种解释是不正确的。请注意,脚注94与该解释等同,只有当它也适用于主叫方不与被叫方交织的意义上时,这又意味着“交织”意味着以“abab”意义交织,因为显然主叫方与交织在较弱的“aba”意义上的被调用者。另外,备选方案2和3在编译器内联函数并执行相同类型的优化以激发为什么表达式i = i++具有未定义行为的情况下似乎是合理的。

+0

pas de probleme。 – wildplasser

回答

11

[我的答案是基于简单的C99标准,而事实上,这是非常不可能的C11将引入重大更改:]

这段代码的这种行为是明确的:main返回0 。在return声明(参见C99,附录C)中的完整表达式之后有一个序列点,因此i++的副作用在maini赋值之前生效。

+0

感谢这个相当满意的答案。尽管C99中的测序整体上不如C11更精细,更隐含,但在本例中它实际上更加明确;我同意C99的解释是非常困难的,因此它不能保证主要收益为0。 – user1480833