是i = post的行为

考虑下面的C程序:

int i = 0;

int post_increment_i() { return i++; }

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

关于2011版C标准(称为C11),下列哪一种替代方案是正确的:

  • C11保证主返回0。
  • C11保证主返回0或1。
  • 根据C11,此程序的行为未定义。
  • 来自C11标准的相关片段:

  • 5.1.2.3程序执行

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

    之前排序的是由单个线程执行的评估之间的不对称,传递,成对关系,这会导致这些评估之间的偏序。 给定任何两个评估A和B,如果A在B之前被排序,那么A的执行应该在执行B之前。(相反,如果A在B之前被排序,那么B在A之后被排序)。如果A没有被排序在B之前或之后,A和B是不确定的。 当A在B之前或之后被测序时,评估A和B被不确定地排序,但是它没有被指定。[13]在评估表达式A和B之间存在一个序列点意味着每个与A有关的值计算和副作用是在与B相关的每个值计算和副作用之前进行排序(顺序点的汇总在附录C中给出)

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

  • 6.5表达式

    表达式是一系列运算符和操作数,用于指定值的计算,指定对象或函数,或者生成副作用或执行其组合。 运算符操作数的值计算在运算符结果的值计算之前排序。

    如果对标量对象的副作用与相同标量对象的不同副作用或使用相同标量对象的值进行值计算相反,则行为未定义。 如果表达式的子表达式有多个可允许的排序顺序,那么如果在任何顺序中发生这种无顺序的副作用,则行为是不确定的。

  • 6.5.2.2函数调用

    在函数标识符和实际参数的评估之后,但在实际调用之前有一个序列点。 在执行被调用函数之前或之后,调用函数中的每个评估(包括其他函数调用)都没有被明确地排序,就被调用函数的执行而言是不确定的。

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

  • 6.5.2.4后缀增量和减量运算符

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

  • 6.5.16作业

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

  • 6.8语句和块

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

  • 以上三种备选方案分别对应以下三种情况:

  • 后缀增量运算符的副作用在main中的赋值之前进行排序。
  • 后缀增量运算符的副作用在main中的赋值之前或之后进行排序,而C11不指定其中的哪一个。 (换句话说,这两种副作用是不确定的。)
  • 这两种副作用是无法确定的。
  • 看起来,第一种选择可以通过下面的推理链得到:

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

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

  • 总的来说,这看起来像一个非常强大的论据。 此外,它对应于人们会直观地考虑最可能的选择。

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


    [我的回答是基于更简单的C99标准,以及C11不太可能引入突破性变化的事实:]

    这段代码的这种行为是明确定义的: main返回0 。 在return语句中的完整表达式之后有一个序列点(见C99,附录C),所以i++副作用在对main赋予i之前生效。

    链接地址: http://www.djcxy.com/p/73191.html

    上一篇: Is the behavior of i = post

    下一篇: Scoping and passing classes by value?