奇怪的C优先评估

时间:2017-05-19 20:06:24

标签: c operator-precedence

有人能解释一下这段代码中的优先级是怎么回事吗?我一直想弄清楚自己发生了什么,但我无法独自处理。

#include <stdio.h>

int main(void) {
   int v[]={20,35,76,80};
   int *a;
   a=&v[1];
   --(*++a);

  printf("%d,%d,%d,%d\n",v[0],v[1],v[2],v[3]);

  (*++a);

  printf("%d\n", *a);

  *a--=*a+1; // WHAT IS HAPPENING HERE?

  printf("%d\n", *a);

  printf("%d,%d,%d,%d\n",v[0],v[1],v[2],v[3]);

}


//OUTPUT
20,35,75,80
80
75
20,35,75,76

3 个答案:

答案 0 :(得分:2)

  

*a--=*a+1; // WHAT IS HAPPENING HERE?

正在发生的行为是 undefined

6.5表达式
...
2如果相对于不同的副作用,对标量对象的副作用未被排序 在相同的标量对象上或使用相同标量的值进行值计算 对象,行为未定义。如果有多个允许的排序 表达式的子表达式,如果这样一个未经测序的一方,则行为是不确定的 任何顺序都会产生影响。 84)

3运算符和操作数的分组由语法指示。 85)除非另有说明 后来,子表达的副作用和价值计算都没有排序。 86)

C 2011 Online Draft (N1570)

表达式*a--*a相对于彼此未经过排序。除少数情况外,C不保证表达式从左到右进行评估;因此,不能保证在*a--之前评估*a(并应用副作用)。

*a--有副作用 - 它会更新a以指向序列中的上一个元素。 *a + 1是一个值计算 - 它将a当前指向的值加1。

根据*a--*a的评估顺序以及实际应用--运算符的副作用,您可以指定v[1] + 1的结果到v[0],或v[1] + 1v[1],或v[0] + 1v[0],或v[0] + 1v[1],或别的东西

由于行为是 undefined ,编译器不需要特别做任何事情 - 可能发出诊断并停止翻译,可能 em>发出诊断并完成翻译,或者可以完成翻译而无需诊断。在运行时,代码可能崩溃,可能获得意外结果,或者代码可能按预期工作。

答案 1 :(得分:1)

我不打算解释整个节目;我将专注于&#34;发生在这里的事情&#34;线。我想我们可以同意,在此行之前,v[]数组看起来像这样,a指向v的最后一个元素:

   +----+----+----+----+
v: | 20 | 35 | 75 | 80 |
   +----+----+----+----+
      0    1    2    3
                     ^
                   +-|-+
                a: | * |
                   +---+

现在,我们有

*a-- = *a+1;

看起来这将分配到a点的位置,并递减a。所以看起来它会为v[3]指定一些内容,但让a指向v[2]

分配的值显然是a指向的值加上1.

但关键问题是,当我们在右侧采用*a+1时,它会在减少之前或之后使用a的旧值或新值。右侧?事实证明这是一个非常非常难以回答的问题。

如果我们在减量后取值,它将被a[2]加上1或76分配给a[3]。它看起来就像你的编译器如何解释它。这有一定的意义,因为当我们从左到右阅读时,很容易想象当我们开始计算*a+1时,a--已经发生了

或者,如果我们在减量之前取值,那么a[3]加1或81将被分配给a[3]。这就是我试过的三种不同编译器的解释方式。这也有一定的意义,因为当然分配实际上是从右到左进行的,所以很容易想象*a+1 {{1}之前发生在左侧。

那么哪个编译器是正确的,你的还是我的,哪个错了?这是答案有点奇怪和/或令人惊讶的地方。答案是既没有编译器错误。这是因为事实证明,这不仅仅是很难确定应该在这里发生什么,而是(根据定义)不可能来弄清楚这里发生了什么。 C标准没有定义该表达式的行为方式。事实上,它比定义表达式应该如何表现更远:C标准明确表示该表达式是 undefined 。因此,你的编译器在a--中放置76是正确的,而我的编译器是正确的,因为&#34;未定义的行为&#34;意味着任何可能发生,编译器安排将其他数字放入v[3]或最终分配给{{1}之外的其他内容并不是错误的。 }}

所以答案的另一部分是你不能写这样的代码。您不能依赖于未定义的行为。它会在不同的编译器下做不同的事情。它可能会做一些完全不可预测的事情。理解,维护或解释是不可能的。

由于评估顺序模糊,表达未定义时很容易检测到。有两种情况:(1)同一变量被修改两次,如v[3]。 (2)同一个变量在一个地方被修改,并在另一个地方使用,如v[3]

值得注意的是,我使用过的三个编译器之一&#34; eo.c:15:警告:无序修改和访问&#39;&#39;&#34;和另一个说&#34; eo.c:15:5:警告:'a'上的操作可能未定义&#34;。如果您的编译器有选项可以启用这些警告,请使用它! (在gcc下x++ + x++*a-- = *a+1。在clang下,它是-Wsequence-point-Wall。)

请参阅John Bode的答案,了解C标准中的详细语言,该表达式未定义。另请参阅有关此主题的规范StackOverflow问题Why are these constructs (using ++) undefined behavior?

答案 2 :(得分:-1)

不完全确定您遇到问题的表达方式。增量和减量运算符具有最高优先级。取消引用之后。加,减,后。

但是关于作业,C没有指定评价顺序(从右到左或从左到右)。 will right hand side of an expression always evaluated first

C没有指定首先评估=运算符的右侧或左侧。

*a--=*a+1;

所以可能是你的指针a首先递减或者在右侧取消引用之后。

换句话说,根据编译器,这个表达式可以等同于:

a--;
*a = *a+1;

*(a-1)=*a+1;
a--;

我个人从不过分依赖我的代码中的运算符优先级。我将括号或分开放在不同的行中使其更清晰。

除非您自己构建编译器并且需要决定要生成哪个汇编代码。