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

use*_*706 18 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);

}
Run Code Online (Sandbox Code Playgroud)

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

也就是说,a1应该是11a2平等10.

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

das*_*ght 37

但是,我希望括号b1在赋值之前增加a1

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

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

a1 = ++b1;
Run Code Online (Sandbox Code Playgroud)


ask*_*ish 33

引自C99:6.5.2.4:

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

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

在你的问题中,只是添加一个括号不会改变序列点,只有;角色会这样做.

或者换句话说,您可以将其视为临时副本,b并且副作用是原始b递增的.但是,在达到序列点之前,所有评估都是在临时副本上完成的b.b然后丢弃临时副本,当到达序列点时,副作用即增量操作被提交给存储.


Ste*_*mit 23

圆括号可能很难想到.但他们并不是说,"确保内部的一切都先发生".

假设我们有

a = b + c * d;
Run Code Online (Sandbox Code Playgroud)

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

a = (b + c) * d;
Run Code Online (Sandbox Code Playgroud)

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

 a = x() + y() * z();
Run Code Online (Sandbox Code Playgroud)

现在,虽然很明显,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; }
Run Code Online (Sandbox Code Playgroud)

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

 a = (x() + y()) * z();
Run Code Online (Sandbox Code Playgroud)

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

最后,重要的是要意识到表达式就像i++做两件事:它们i取值并为其加1,然后将新值存储回来i.但是商店重新进入i并不一定会马上发生,它可能会在以后发生.还有一个问题是"商店什么时候回来i?" 有点像"函数x何时被调用?"的问题.你无法真正告诉它,它取决于编译器,它通常无关紧要,它将不同于编译器,如果你真的在乎,你将不得不做其他事情来强制命令.

在任何情况下,请记住,它的定义i++是它给出了周围表达式的i.这是一个非常绝对的规则,只是通过添加一些括号不能改变它!这不是括号的作用.

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

x = z() + ((y() * z())?
Run Code Online (Sandbox Code Playgroud)

我可以写出来,但它不会改变任何东西.请记住,括号并不意味着"先做所有事情 ".它们确实会导致乘法在加法之前发生,但编译器无论如何都已经这样做了,基于乘法相对于加法的更高优先级.

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

c = y();
d = z();
b = x();
a = b + c * d;
Run Code Online (Sandbox Code Playgroud)

在您的情况下,如果您想确保将b的新值分配给a,则可以编写

c = b++;
a = b;
Run Code Online (Sandbox Code Playgroud)

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

a = ++b;
Run Code Online (Sandbox Code Playgroud)


AnT*_*AnT 6

你的期望是完全没有根据的.

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

此外,根据定义,后增量表达式b++评估为原始b.无论您添加多少对括号,此要求都将保留b++.即使括号以某种方式"强制"即时增量,语言仍然需要(((b++)))评估b,这意味着a仍然可以保证接收非递增b.

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


nic*_*kie 5

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

a = 2*(b+1);
Run Code Online (Sandbox Code Playgroud)

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

a = 2*b+1;
Run Code Online (Sandbox Code Playgroud)

你的意思是b应该加倍,然后结果应该递增.这些分配的两个语法树是:

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

a = 2*(b+1);            a = 2*b+1;
Run Code Online (Sandbox Code Playgroud)

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

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

a1 = (b1++);
a2 = b2++;
Run Code Online (Sandbox Code Playgroud)

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

    =
   / \
  a   ++ (postfix)
      |
      b
Run Code Online (Sandbox Code Playgroud)

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