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

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

 #include  #include  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 。 但是,我希望括号在其值分配给a1之前使b1递增。

即, a1应为11a2等于10

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

引自C99:6.5.2.4:

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

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

在你的问题中,只是添加一个括号不会改变序列点,只有; 性格做到了。

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

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

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

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

 a1 = ++b1; 

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

假设我们有

 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++这样的表达式会做两件事:它们取值并为其加1,然后将新值存储回i 。 但是回到i的商店并不一定会马上发生,它可能会在以后发生。 还有一个问题是“ i的商店什么时候回来了?” 有点像“函数x什么时候被调用?”的问题。 你无法真正告诉它,它取决于编译器,它通常无关紧要,它将不同于编译器,如果你真的在乎,你将不得不做其他事情来强制命令。

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

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

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

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

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

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

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

 c = b++; a = b; 

但当然这很愚蠢 – 如果您只想增加b并将其新值分配给a,那就是++的前缀:

 a = ++b; 

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

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

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

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

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

 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 。 这是你观察到的,这是正确的行为。

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

您没有将b1分配给a1 :您正在分配postincrement表达式的结果。

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

 #include  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 " << ax << endl; return 0; } 

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

 b is 11 when operator= is executed a is 10 

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

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

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

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

它等于,

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

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

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

来自learn.geekinterview.com :

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

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

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

在你的情况下,如果你想先增加b ,你应该使用预增量, ++b而不是b++(b++)

更改

 a1 = (b1++); 

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

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

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

因为括号不会改变这里的值。

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