i = post_increment_i()的行为是指定的,未指定的还是未定义的?

时间:2012-06-25 20:36:20

标签: c language-lawyer undefined-behavior operator-precedence sequence-points

考虑以下C程序:

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,此程序的行为未定义。
  4. C11标准的相关摘要:

    • 5.1.2.3程序执行

        

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

           

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

           

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

    • 6.5表达式

        

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

           

      如果相对于不同的副作用,对标量对象的副作用未被排序   在相同的标量对象上或使用相同标量的值进行值计算   对象,行为未定义。如果有多个允许的排序   表达式的子表达式,如果这样一个未经测序的一方,则行为是不确定的   效果发生在任何排序中。

    • 6.5.2.2函数调用

        

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

           

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

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

        

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

    • 6.5.16作业

        

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

    • 6.8语句和块

        

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

    上述三种选择分别对应于以下三种情况:

    1. 后缀增量运算符的副作用在main中赋值之前排序。
    2. 后缀增量运算符的副作用在main中赋值之前或之后排序,而C11没有指定哪个。 (换句话说,这两种副作用是不确定的。)
    3. 这两种副作用都没有结果。
    4. 似乎第一种选择通过以下推理链来保持:

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

      • 考虑规则更新左操作数的存储值的副作用是 在左右操作数的值计算之后排序。此规则排除了上述情况I。因此,案例II成立。 QED

      总的来说,这看起来非常强大。此外,它对应于人们直觉上认为最可能的替代方案。

      然而,它确实依赖于术语的特定解释"评估"和"执行被调用函数" (假设A和B)和一个不完全直接的推理线,所以我想把它放在那里,看看人们是否有理由相信这种解释是不正确的。请注意,脚注94与此解释等同,只有在呼叫者不与被呼叫者交错的意义上也适用,这反过来意味着"交错"意味着在" abab"感觉,因为显然呼叫者在较弱的" aba"中与被叫者交错。感。此外,在编译器内联函数然后执行相同类型的优化以激发表达式i = i++具有未定义行为的原因的情况下,备选方案2和3似乎是合理的。

1 个答案:

答案 0 :(得分:12)

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

此代码的此行为定义明确:main返回0。在return语句中完整表达后立即有一个序列点(参见C99,附录C),因此i++的副作用在i的{​​{1}}赋值之前生效{1}}。