为什么a =(b ++)与a = b ++具有相同的行为?

时间:2015-06-27 10:12:00

标签: c gcc

我正在C中编写一个小型测试应用程序,并在我的Ubuntu 14.04上预装了GCC 4.8.4。我对表达式a=(b++);a=b++;的行为方式相同这一事实感到困惑。使用以下简单代码:

#include <stdint.h>
#include <stdio.h>

int main(int argc, char* argv[]){
    uint8_t a1, a2, b1=10, b2=10;
    a1=(b1++);
    a2=b2++;

    printf("a1=%u, a2=%u, b1=%u, b2=%u.\n", a1, a2, b1, b2);

}

gcc编译后的结果是a1=a2=10,而b1=b2=11。但是,我希望括号在其值分配给b1之前增加a1

即,a1应为11a2等于10

有没有人对这个问题有所了解?

10 个答案:

答案 0 :(得分:37)

  

但是,我希望括号在a1

的值分配之前增加b1

你不应该期望:在增量表达式周围放置圆括号不会改变其副作用的应用。

副作用(在这种情况下,它意味着将11写入b1)在检索++的当前值后应用一段时间。这可能发生在完全评估完整赋值表达式之前或之后。这就是为什么后增量将保持后增量,包括或不包括括号。如果您想要预增量,请在变量之前放置a1 = ++b1;

<html>
    <body>
        <form method="post" action="b.php" enctype="multipart/form-data">
            <input id="file" name="file" type="file">
            <input value="upload" name="submit" type="submit">
        </form>
    </body>
</html>

答案 1 :(得分:32)

引自C99:6.5.2.4:

  

postfix ++运算符的结果是操作数的值。   获得结果后,操作数的值递增。   (即,将相应类型的值1添加到其中。)请参阅   关于加法算子和复合赋值的讨论   有关约束,类型和转换以及影响的信息   对指针的操作。更新存储值的副作用   操作数应在前一个和下一个序列之间发生   点。

您可以查看C99:附录C以了解有效序列点是什么。

在你的问题中,只是添加一个括号不会改变序列点,只有;字符才会这样做。

或者换句话说,您可以查看它,就像b的临时副本一样,副作用是原始b递增的。但是,在达到序列点之前,所有评估都在b的临时副本上完成。然后丢弃b的临时副本,当达到序列点时,副作用即递增操作被提交到存储。

答案 2 :(得分:23)

圆括号可能很难想到。但他们意味着,“确保首先发生所有内容”。

假设我们有

a = b + c * d;

乘法优先于加法的优先级告诉我们编译器会安排将c乘以d,然后将结果添加到b。如果我们想要其他解释,我们可以使用括号:

a = (b + c) * d;

但是假设我们将一些函数调用抛入混合中。也就是说,假设我们写了

 a = x() + y() * z();

现在,虽然很明显y()的返回值将乘以z()的返回值,但我们可以说x(),y()和z()的顺序是什么叫进来?答案是,不,我们绝对不能!如果您完全不确定,我邀请您尝试使用x,y和z这样的函数:

int x() { printf("this is x()\n"); return 2; }
int y() { printf("this is y()\n"); return 3; }
int z() { printf("this is z()\n"); return 4; }

我第一次尝试这个时,使用我前面的编译器,我发现函数x()首先被调用,即使最后需要它的结果。当我将调用代码更改为

 a = (x() + y()) * z();

对x,y和z的调用顺序保持完全相同,编译器只是安排以不同的方式组合它们的结果。

最后,重要的是要意识到像i++这样的表达式会做两件事:它们取i的值并向其添加1,然后将新值存储回{{1} }。但是回到i的商店并不一定会马上发生,它可能会在以后发生。以及“商店何时回到i确实发生了什么?”有点像“函数x何时被调用?”的问题。你无法真正告诉它,这取决于编译器,它通常无关紧要,它会因编译器而异,如果你真的在乎,你将不得不做其他事情来强制命令。

在任何情况下,请记住i的定义是它将{em>旧值i++输出到周围的表达式中。这是一个非常绝对的规则,只需添加一些括号就可以进行更改!这不是括号所做的。

让我们回到上一个涉及函数x,y和z的例子。我注意到函数x首先被调用。假设我不想要那个,假设我想要首先调用函数y和z。我可以通过写

来实现这一目标
i

我可以写出来,但它不会改变任何东西。请记住,括号并不意味着“先将所有内容”放在首位。它们确实会导致乘法在加法之前发生,但编译器无论如何都已经这样做了,基于乘法优先于加法的优先级。

上面我说,“如果你真的在乎,你将不得不做一些别的事情来强迫订单”。您通常需要做的是使用一些临时变量和一些额外的语句。 (技术术语是“插入一些序列点。”)例如,为了使y和z首先被调用,我可以写

x = z() + ((y() * z())?

在你的情况下,如果你想确保b的新值被分配给a,你可以写

c = y();
d = z();
b = x();
a = b + c * d;

但当然这很愚蠢 - 如果您想要做的就是增加b并将其新值分配给a,那就是前缀c = b++; a = b; 的用途:

++

答案 3 :(得分:6)

你的期望完全没有根据。

括号对执行顺序没有直接影响。它们不会在表达式中引入序列点,因此它们不会强制任何副作用早于它们在没有括号的情况下实现。

此外,根据定义,增量后表达式b++的计算结果为b原始值。无论您在b++附近添加多少对括号,此要求都将保留。即使括号以某种方式“强制”即时增量,该语言仍然需要(((b++)))来评估b值,这意味着a仍然会保证收到b非递增值。

括号仅影响运算符及其操作数之间的语法分组。例如,在您的原始表达式a = b++中,您可能会立即询问++苹果到ba = b的结果。在您的情况下,通过添加括号,您只需显式强制++运算符应用于(与...分组)b操作数。但是,根据语言语法(以及从中派生的运算符优先级和关联性),++已经适用于b,即一元++的优先级高于二进制= 。你的括号没有改变任何东西,它只是重复了已经隐含的分组。因此行为没有变化。

答案 4 :(得分:5)

括号完全是句法。它们只是对表达式进行分组,如果要覆盖运算符的优先级或关联性,它们很有用。例如,如果您在此处使用括号:

a = 2*(b+1);

你的意思是b+1的结果应该加倍,而如果你省略括号:

a = 2*b+1;

你的意思是只要b加倍,然后结果应该递增。这些赋值的两个语法树是:

   =                      =
  / \                    / \
 a   *                  a   +
    / \                    / \
   2   +                  *   1
      / \                / \
     b   1              2   b

a = 2*(b+1);            a = 2*b+1;

通过使用括号,您可以更改与您的程序对应的语法树,(当然)不同的语法可能对应于不同的语义。

另一方面,在你的程序中:

a1 = (b1++);
a2 = b2++;

括号是多余的,因为赋值运算符的优先级低于后缀增量(++)。这两项任务是等同的;在这两种情况下,相应的语法树如下:

    =
   / \
  a   ++ (postfix)
      |
      b

现在我们已经完成了语法,让我们转到语义。此声明表示:评估b++并将结果分配给a。评估b++会返回b的当前值(程序中为10),作为副作用,会增加b(现在变为11)。返回的值(即10)将分配给a。这是你观察到的,这是正确的行为。

答案 5 :(得分:3)

好的,简而言之:b++一元表达式,并且它周围的括号不会影响算术运算的优先级,因为++增量运算符具有C中最高(如果不是 最高)优先级之一。在a * (b + c)中,(b + c)二进制表达式(不要与二进制编号系统混淆!)因为变量b及其加数c。因此可以很容易地记住这样:括号放在二进制,三元,四元... + INF表达式几乎总是会影响优先级(*);围绕一元的括号绝不会 - 因为它们“足够强大”以“承受”括号分组。

(*)像往常一样,规则有一些例外,如果只有少数:e。 G。尽管是二元运算符,->(访问结构上的指针成员)具有非常强的绑定。但是,C 初学者很可能需要很长时间才能在代码中编写->,因为他们需要对指针和结构有深入的了解。 < / p>

答案 6 :(得分:3)

  

但是,我希望括号在将值分配给a1之前使b1递增。

您没有将b1分配给a1:您正在指定后增量表达式的结果。

考虑以下程序,该程序在执行赋值时打印b的值:

#include <iostream>
using namespace std;

int b;

struct verbose
{
    int x;

    void operator=(int y) {
        cout << "b is " << b << " when operator= is executed" << endl;  
        x = y;
    }
};

int main() {
    // your code goes here
    verbose a;
    b = 10;
    a = b++;
    cout << "a is " << a.x << endl;
    return 0;
}

我怀疑这是未定义的行为,但是当使用ideone.com时,我得到如下所示的输出

b is 11 when operator= is executed
a is 10

答案 7 :(得分:2)

括号不会改变后增量​​行为本身。

  

A1 =(B1 ++); // B1 = 10

等于,

 uint8_t mid_value = b1++; //10
 a1 = (mid_value); //10

答案 8 :(得分:1)

++放在语句的末尾(称为后递增),意味着在语句之后完成增量。

即使将变量括在括号中也不会改变语句完成后递增的事实。

来自learn.geekinterview.com

  

在后缀形式中,增量或减量发生在表达式求值中使用该值之后。

     

在前缀增量或减量操作中,增量或减量发生在表达式求值中使用该值之前。

这就是a = (b++)a = b++在行为方面相同的原因。

在您的情况下,如果您想先增加b,则应使用预增量,++b代替b++(b++)

更改

a1 = (b1++);

a1 = ++b1; // b will be incremented before it is assigned to a.

答案 9 :(得分:1)

简而言之: 语句完成后,b ++会递增

但即使在那之后,b ++的结果也被放到了。

由于括号不会更改此处的值。

(如果查看操作的java字节码,可以看到这个)