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

问题描述 投票:6回答:1

考虑以下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,该程序的行为未定义。

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)换句话说,函数执行不会相互“交错”。
  • 6.5.2.4后缀增量和减量运算符 postfix ++运算符的结果是操作数的值。作为副作用,操作数对象的值递增(即,将相应类型的值1添加到其中)。 [...]在更新操作数的存储值的副作用之前,对结果的值计算进行排序。对于不确定顺序的函数调用,后缀++的操作是单个评估。
  • 6.5.16分配 赋值运算符将值存储在左操作数指定的对象中。 [...]在左右操作数的值计算之后,对更新左操作数的存储值的副作用进行排序。对操作数的评估是不确定的。
  • 6.8声明和块 完整表达式是不属于另一个表达式或声明符的表达式。以下各项都是完整表达式:[...]表达式语句中的表达式; [...] return语句中的(可选)表达式。在完整表达式的评估和要评估的下一个完整表达式的评估之间存在序列点。

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

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

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

  • 考虑规则调用函数中的每个评估(包括其他函数调用)在执行被调用函数体之前或之后没有特别排序,关于被调用函数的执行是不确定的。在6.5.2.2中。假设A:主要的赋值运算符的副作用是这样的“评估”。假设B:短语“被调用函数的执行”包括后缀增量运算符的值计算和后缀增量运算符的副作用。根据这些假设和上述规则,可以得出:I)值计算和后缀增量运算符的副作用都是在赋值运算符在main中的副作用之前排序,或者II)值计算和副作用在主要的赋值运算符的副作用之后,后缀增量运算符的顺序排序。
  • 考虑规则更新左操作数的存储值的副作用在左右操作数的值计算之后排序。该规则排除了上述情况。因此,案例II成立。 QED

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

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

c language-lawyer undefined-behavior operator-precedence sequence-points
1个回答
12
投票

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

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

© www.soinside.com 2019 - 2024. All rights reserved.