首页 文章

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

提问于
浏览
6

考虑以下C程序:

int i = 0;

int post_increment_i() { return i++; }

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

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

  • C11保证主要返回0 .

  • C11保证main返回0或1 .

  • 根据C11,此程序的行为未定义 .

C11标准的相关摘要:

  • 5.1.2.3程序执行

访问易失性对象,修改对象,修改文件或调用执行任何这些操作的函数都是副作用,这些都是执行环境状态的变化 . 表达式的评估通常包括值计算和副作用的启动 . 左值表达式的值计算包括确定指定对象的身份 . 之前排序的是由单个线程执行的评估之间的不对称,传递,成对关系,其在这些评估中引起部分顺序 . 给定任何两个评估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函数调用

在评估函数指示符和实际参数之后但在实际调用之前有一个序列点 . 调用函数(包括其他函数调用)中的每个评估(在执行被调用函数体之前或之后没有特别排序)在被调用函数的执行方面是不确定的顺序.94 94)换句话说,函数执行不会相互“交错” .

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

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

  • 6.5.16分配

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

  • 6.8声明和块

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

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

  • 后缀增量运算符的副作用在main中赋值之前排序 .

  • 后缀增量运算符的副作用在main中赋值之前或之后排序,而C11没有指定哪个 . (换句话说,这两种副作用是不确定的 . )

  • 这两种副作用未被排除 .

似乎第一种选择通过以下推理链来保持:

  • 考虑6.5.2.2中的规则 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. . 假设A:副作用主要的赋值运算符是"evaluation" . 假设B:短语"the execution of the called function"包括后缀增量运算符的值计算和后缀增量运算符的副作用 . 根据这些假设和上述规则,可以得出:I)值计算和后缀增量运算符的副作用都是在赋值运算符在main中的副作用之前排序,或者II)值计算和副作用在主要的赋值运算符的副作用之后,后缀增量运算符的顺序排序 .

  • 考虑规则 The side effect of updating the stored value of the left operand is sequenced after the value computations of the left and right operands. 此规则排除了上述情况 . 因此,案例II成立 . QED

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

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

1 回答

  • 11

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

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

相关问题