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

标签 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;
}

对于C版本的2011年版本(称为C11),下列替代方法是正确的:
  • C11保证main返回0。
  • C11保证main返回0或1。
  • 根据C11,此程序的行为未定义。

  • C11标准的相关片段:
  • 5.1.2.3程序执行

    Accessing a volatile object, modifying an object, modifying a file, or calling a function that does any of those operations are all side effects, which are changes in the state of the execution environment. Evaluation of an expression in general includes both value computations and initiation of side effects. Value computation for an lvalue expression includes determining the identity of the designated object.

    Sequenced before is an asymmetric, transitive, pair-wise relation between evaluations executed by a single thread, which induces a partial order among those evaluations. Given any two evaluations A and B, if A is sequenced before B, then the execution of A shall precede the execution of B. (Conversely, if A is sequenced before B, then B is sequenced after A.) If A is not sequenced before or after B, then A and B are unsequenced. Evaluations A and B are indeterminately sequenced when A is sequenced either before or after B, but it is unspecified which.13 The presence of a sequence point between the evaluation of expressions A and B implies that every value computation and side effect associated with A is sequenced before every value computation and side effect associated with B. (A summary of the sequence points is given in annex C.)

    13) The executions of unsequenced evaluations can interleave. Indeterminately sequenced evaluations cannot interleave, but can be executed in any order.

  • 6.5表达式

    An expression is a sequence of operators and operands that specifies computation of a value, or that designates an object or a function, or that generates side effects, or that performs a combination thereof. The value computations of the operands of an operator are sequenced before the value computation of the result of the operator.

    If a side effect on a scalar object is unsequenced relative to either a different side effect on the same scalar object or a value computation using the value of the same scalar object, the behavior is undefined. If there are multiple allowable orderings of the subexpressions of an expression, the behavior is undefined if such an unsequenced side effect occurs in any of the orderings.

  • 6.5.2.2函数调用

    There is a sequence point after the evaluations of the function designator and the actual arguments but before the actual call. Every evaluation in the calling function (including other function calls) that is not otherwise specifically sequenced before or after the execution of the body of the called function is indeterminately sequenced with respect to the execution of the called function.94

    94) In other words, function executions do not ‘‘interleave’’ with each other.

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

    The result of the postfix ++ operator is the value of the operand. As a side effect, the value of the operand object is incremented (that is, the value 1 of the appropriate type is added to it). [...] The value computation of the result is sequenced before the side effect of updating the stored value of the operand. With respect to an indeterminately-sequenced function call, the operation of postfix ++ is a single evaluation.

  • 6.5.16分配

    An assignment operator stores a value in the object designated by the left operand. [...] The side effect of updating the stored value of the left operand is sequenced after the value computations of the left and right operands. The evaluations of the operands are unsequenced.

  • 6.8语句和块

    A full expression is an expression that is not part of another expression or of a declarator. Each of the following is a full expression: [...] the expression in an expression statement; [...] the (optional) expression in a return statement. There is a sequence point between the evaluation of a full expression and the evaluation of the next full expression to be evaluated.


  • 上面的三种选择分别对应以下三种情况:
  • 后缀递增运算符的副作用在main中的赋值之前进行了排序。
  • 后缀增量运算符的副作用在main中的赋值之前或之后进行排序,并且C11不指定哪个。 (换句话说,这两个副作用是不确定地排序的。)
  • 这两个副作用没有顺序。

  • 通过以下一系列推理,似乎第一种选择成立:
  • 考虑规则调用函数中的每个评估(包括
    其他函数调用),否则在此之前或之后没有专门排序
    被调用函数的主体的执行相对于
    被调用函数的执行。 6.5.2.2中的
    。假设A:主要是赋值运算符的副作用是这样的“评估”。假设B:短语“被调用函数的执行”包括后缀增量运算符的值计算和后缀增量运算符的副作用。根据这些假设和上述规则,可以得出以下结论:要么I)后缀增量运算符的值计算和副作用都在赋值运算符main的副作用之前排序,要么II)值计算和副作用后缀增量运算符的后两者都在main中赋值运算符的副作用之后进行排序。
  • 考虑规则,更新左操作数存储值的副作用是
    在左右操作数的值计算之后排序。
    此规则排除上面的情况I。因此,第二种情况成立。 QED

  • 总体而言,这似乎是一个非常有力的论据。而且,它对应于人们直觉上认为最有可能的选择。

    但是,它确实依赖于对术语“评估”和“执行被调用函数”的具体解释(假设A和B)以及不完全直接的推理方法,因此我想将其放在此处以了解人们是否有理由相信这种解释是不正确的。请注意,脚注94仅在调用者不与被调用者交织的意义上适用时,才与此解释等效,这反过来意味着“交织”意味着在“abab”意义上交织,因为显然调用者与被调用者在较弱的“aba”意义上。同样,在编译器内联函数然后执行相同类型的优化(激发表达式i = i++具有未定义行为的原因)的情况下,备选方案2和3似乎是合理的。

    最佳答案

    [我的答案基于更简单的C99标准,并且C11极不可能引入重大变化这一事实:]

    此代码的行为是明确定义的:main返回0return语句中的完整表达式之后立即有一个序列点(请参阅C99,附件C),因此i++的副作用要在i中分配给main之前生效。

    关于c - i = post_increment_i()的行为是指定的,未指定的还是未定义的?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/11196956/

    相关文章:

    c++ - “寻找”优化

    c - OCIObjectGetAttr 和 OCIObjectSetAttr 的 LNK2091 错误

    c++ - 在初始化中省略一个非平凡的复制/移动构造函数是否合法?

    c++ - constexpr 标准仿函数的正确用法是什么?

    c++ - 空结构(或结构填充)可以有任意值吗?

    c++ - 在 C 和 C++ 中,使用逗号运算符(如 "a = b,++a;")的表达式是否未定义?

    将 int 更改为 double 会将我的结果更改为零

    c - 在 C 中分配给复数的实部或虚部

    c++ - 名称查找歧义不一致

    c - C 中未定义的行为实际上会发生什么