6

考虑以下 C 程序:

int i = 0;

int post_increment_i() { return i++; }

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

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

  1. C11 保证 main 返回 0。
  2. C11 保证 main 返回 0 或 1。
  3. 根据 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 函数调用

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

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

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

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

  • 6.5.16 作业

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

  • 6.8 语句和块

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

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

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

通过以下推理链,第一种选择似乎成立:

  • 考虑以下规则:调用函数(包括其他函数调用)中的每个求值,如果在被调用函数的主体执行之前或之后没有特别排序,则相对于被调用函数的执行是不确定的。在 6.5.2.2 中。假设 A:main 中赋值运算符的副作用就是这样的“评估”。假设B:短语“被调用函数的执行”既包括后缀自增算子的值计算,也包括后缀自增算子的副作用。根据这些假设和上述规则,要么 I) 后缀增量运算符的值计算和副作用都排在 main 中赋值运算符的副作用之前,要么 II) 值计算和副作用后缀自增运算符都在 main 中赋值运算符的副作用之后排序。

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

总的来说,这看起来是一个非常有力的论点。此外,它对应于人们直觉上认为最可能的替代方案。

但是,它确实依赖于对术语“评估”和“被调用函数的执行”(假设 A 和 B)的具体解释以及不完全直截了当的推理,所以我想把它放在那里看看人们是否有有理由相信这种解释是不正确的。请注意,脚注 94 仅在它也适用于调用者不与被调用者交错的情况下才与此解释等效,这反过来意味着“交错”意味着“abab”意义上的交错,因为显然调用者与较弱的“aba”意义上的被调用者。还,i = i++

4

1 回答 1

12

[我的回答基于更简单的 C99 标准,而且 C11 极不可能引入重大更改: ]

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

于 2012-06-25T20:41:59.043 回答