考虑以下C程序:
int i = 0;
int post_increment_i() { return i++; }
int main() {
i = post_increment_i();
return i;
}
关于2011版C标准(称为C11),以下哪种选择是正确的:
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语句和块
完整表达式是不属于另一个表达式或声明符的表达式。 以下各项都是完整表达式:[...]表达式语句中的表达式; [...]返回中的(可选)表达式 声明。在完整表达式的评估与之间存在一个序列点 评估下一个要评估的完整表达式。
上述三种选择分别对应于以下三种情况:
似乎第一种选择通过以下推理链来保持:
考虑规则调用函数中的每个评估(包括 其他函数调用)在其他方式之前或之后没有特别排序 被调用函数体的执行是相对于不确定地排序的 在6.5.2.2中执行被调用函数。。假设A:主要的赋值运算符的副作用是"评估"。假设B:短语"被叫函数的执行"包括后缀增量运算符的值计算和后缀增量运算符的副作用。根据这些假设和上述规则,可以得出:I)值计算和后缀增量运算符的副作用都是在赋值运算符在main中的副作用之前排序,或者II)值计算和副作用在主要的赋值运算符的副作用之后,后缀增量运算符的顺序排序。
考虑规则更新左操作数的存储值的副作用是 在左右操作数的值计算之后排序。此规则排除了上述情况I。因此,案例II成立。 QED
总的来说,这看起来非常强大。此外,它对应于人们直觉上认为最可能的替代方案。
然而,它确实依赖于术语的特定解释"评估"和"执行被调用函数" (假设A和B)和一个不完全直接的推理线,所以我想把它放在那里,看看人们是否有理由相信这种解释是不正确的。请注意,脚注94与此解释等同,只有在呼叫者不与被呼叫者交错的意义上也适用,这反过来意味着"交错"意味着在" abab"感觉,因为显然呼叫者在较弱的" aba"中与被叫者交错。感。此外,在编译器内联函数然后执行相同类型的优化以激发表达式i = i++
具有未定义行为的原因的情况下,备选方案2和3似乎是合理的。
答案 0 :(得分:12)
[我的回答是基于更简单的C99标准,以及C11极不可能引入突破性变化的事实:]
此代码的此行为定义明确:main
返回0
。在return
语句中完整表达后立即有一个序列点(参见C99,附录C),因此i++
的副作用在i
的{{1}}赋值之前生效{1}}。