【问题标题】:I have a question about precedence rules in C我对 C 中的优先规则有疑问
【发布时间】:2020-08-05 15:02:14
【问题描述】:

在以下函数中:

int fun(int *k) {
    *k += 4;
    return 3 * (*k) - 1;
}

void main() {
    int i = 10, j = 10, sum1, sum2;
    sum1 = (i / 2) + fun(&i);
    sum2 = fun(&j) + (j / 2);
}

总和 1 等于 46,总和 2 等于 48。如果没有优先规则,函数将如何运行?

如果没有一致的优先规则,事情会有多大的不同?

【问题讨论】:

  • 这是未定义的行为。 fun(&i)i 有副作用,但您可以在序列点之前的同一语句中访问它。
  • 为了绕过未定义的行为,假设你写了sum1 = (i / 2) + 10;。如果没有优先规则(但推测二元运算符和括号中的子表达式分组仍然存在从左到右的关联性),sum1 = (i / 2) + 10; 将被评估为(sum1 = (i / 2)) + 10;
  • 运算符优先级就像自然语言中的语法一样,允许在句子中决定谁是主语,谁是附加主语。没有优先级就像没有语法,在那种情况下你无法理解那句话;-)
  • @bruno Master Yoda 同意不要。
  • @EugeneSh.: 不,行为不是未定义的。在函数参数的评估和函数的调用之间以及函数中的每个完整表达式(包括返回)之后都有序列点。该行为未指定,因为未确定 i/2fun(&x) 之间的评估顺序,但它不是未定义的。

标签: c function operator-precedence rules


【解决方案1】:

优先级规则告诉我们表达式是如何结构化,而不是如何求值。在sum1 = (i / 2) + fun(&i); 中,规则告诉我们的内容包括:

  • i / 2 被组合在一起,因为它在括号中;它不能形成,例如,(sum1 = i) / 2 + fun(&i);
  • (i / 2)fun(&i) 被组合在一起,因为 + 的优先级高于 =,使得 sum1 = ((i / 2) + fun(&i); 而不是 (sum1 = (i / 2)) + fun(&i);

优先规则不会告诉我们是先评估 i / 2 还是 fun(&i)。事实上,C 标准中没有规定是先评估 i / 2 还是 fun(&i)。编译器可以选择。

如果首先评估i / 2,则结果将是10 / 2 + 41,然后是5 + 41,最后是46。如果首先评估fun(&i),则结果将是14 / 2 + 41,然后是7 + 41,最后是48。您的编译器选择了前者。它本可以选择后者。

如果没有优先规则,函数将如何运行?

如果没有规则,我们将不知道函数将如何执行。规则告诉我们如何执行。

一些 cmets 断言这个程序的行为是未定义的。这是不正确的。这种误解来自C 2018 6.5 2,它说:

如果标量对象的副作用相对于同一标量对象的不同副作用或使用同一标量对象的值的值计算是未排序的,则行为未定义......

在您的代码中,i / 2 使用 ifun(&i) 包含对 i 的“副作用”(通过赋值更改其值)。如果这些是未排序的,则行为将是未定义的。但是,在评估fun 的参数之后和调用它之前都有一个序列点,并且在fun 中的每个完整表达式之后都有一个序列点,包括它的return 语句。因此,i 的使用及其副作用有一些顺序。这种排序不完全由 C 标准的规则确定,但按照标准的定义,它是不确定排序的,而不是未排序的。

【讨论】:

  • 跟进问题上的 cmets,然后,您对序列点的强调是错误的。他们确实确保在计算函数返回值之前对i 的副作用进行排序,这是必要的,但唯一可以不确定地评估i / 2fun(&i)(以及后者的副作用) 6.5.2.10 的特殊情况规定是相对于彼此排序的,而不是未排序的,它赋予函数调用子表达式该特殊属性。也有类似的案例无法挽救。
  • @JohnBollinger:答案不强调序列点。该信息在要点之后,没有加粗或以其他方式特别标记。除了纠正 cmets 中的错误信息外,我不会将其包括在内。问题与序列点无关,我无意深入研究该主题,而不是纠正错误的 cmets。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-01-03
  • 1970-01-01
  • 1970-01-01
  • 2021-03-29
  • 2020-05-27
相关资源
最近更新 更多