为什么c = ++(a + b)给出编译错误?


111

经过研究,我了解到增量运算符要求操作数具有可修改的数据对象:https : //en.wikipedia.org/wiki/Increment_and_decrement_operators

由此我猜想它会产生编译错误,因为它(a+b)是一个临时整数,因此不可修改。

这种理解正确吗?这是我第一次尝试研究问题,因此,如果有什么我应该寻找的,请提出建议。


35
就研究而言,这还不错。您走在正确的轨道上。
StoryTeller-Unslander Monica,

35
您希望该表达式做什么?
qrdl18年

4
根据C11标准6.5.3.1:前缀递增或递减运算符的操作数应具有原子,限定或不限定的实数或指针类型,并且应为可修改的左值
Christian Gibbons,

10
您如何希望1在a和b之间分配?“数组索引应该从0还是1开始?我认为没有适当考虑就拒绝了我的0.5折衷。” — Stan Kelly-Bootle
安德鲁·莫顿

5
我认为有一个后续问题是,当c = a + b + 1您的意图更清晰,类型也更短时,为什么您要这样做呢?增量/减量运算符做两件事:1.它们和它们的参数形成一个表达式(可以在例如for循环中使用),2.他们修改参数。在您的示例中,您使用的是属性1.,而不是属性2.,因为您丢弃了修改后的参数。如果不需要属性2,而只需要表达式,则可以编写一个表达式,例如x + 1而不是x ++。
Trevor

Answers:


117

这只是一条规则,仅此而已,并且可能(1)使编写C编译器更容易,以及(2)没有人说服C标准委员会放松它。

非正式地说,您只能写++foofoo可以出现在赋值表达式的左侧,例如foo = bar。由于您不会写a + b = bar,所以您也不会写++(a + b)

没有真正的理由为什么a + b不能产生一个++可以在其上进行操作的临时文件,其结果就是expression的值++(a + b)


4
我认为要点(1)击中了头。仅查看C ++中临时实现的规则可能会让您大吃一惊(但是,它确实很强大)。
StoryTeller-Unslander Monica

4
@StoryTeller:确实,与我们钟爱的C ++语言不同,C仍然相对容易地编译成汇编语言。
Bathsheba

29
恕我直言,这是一个真正的原因:如果++有时会产生某些修改而有时却没有的副作用,那将是一个可怕的混乱。
aschepler

5
@dng:确实是这样;这就是为什么引入术语左值(左值)和右值(右值)的原因,尽管事情要比如今复杂得多(尤其是在C ++中)。例如,常量永远不能是左值:类似5 = a的东西是没有意义的。
Bathsheba

6
@Bathsheba解释了为什么5 ++也会导致编译错误
dng

40

C11标准在6.5.3.1节中规定

前缀递增或递减运算符的操作数应具有原子,合格或不合格的实数或指针类型,并且应为可修改的左值

在第6.3.2.1节第1小节中描述了“可修改的左值”

左值是可能指定对象的表达式(对象类型不是void)。如果左值在评估时未指定对象,则该行为未定义。当说一个对象具有特定类型时,该类型由用于指定该对象的左值指定。 可修改的左值是不具有数组类型,不完整类型,不具有const限定类型的左值,并且如果是结构或联合,则不具有任何成员(包括递归地包括任何成员)或const限定类型的所有包含的集合或联合的元素)。

因此(a+b)不是可修改的左值,因此不适合使用前缀增量运算符。


1
您从这些定义中得出的结论缺失了……您想说(a + b)可能没有指定对象,但是这些段落不允许这样做。
hkBst

21

你是对的。在++尝试将新的值赋给原始变量。因此++a将取的值a,将1其添加到,然后将其分配回a。如您所说,由于(a + b)是一个临时值,而不是具有分配的内存地址的变量,因此无法执行分配。


12

我想您大部分回答了您自己的问题。我可能会对您的措辞进行一些小的更改,然后将C.Gibbons所提到的“临时变量”替换为“ rvalue”。

随着您了解C的内存模型,术语变量,参数,临时变量等将变得更加清晰(这看起来像是一个不错的概述:https : //www.geeksforgeeks.org/memory-layout-of-c-program/)。

当您刚开始时,“ rvalue”一词似乎是不透明的,因此我希望以下内容有助于您建立直觉。

左值/右值在谈论等号的不同侧(赋值运算符):左值=左手侧(小写L,而不是“一个”)右值=右手侧

稍微了解C如何使用内存(和寄存器)将有助于理解为什么区别很重要。在广泛的笔触,编译器创建的该计算表达式(右值)的结果的机器语言指令的列表,然后该结果某处(左值)。想象一下一个编译器处理以下代码片段:

x = y * 3

在汇编伪代码中,它可能类似于以下玩具示例:

load register A with the value at memory address y
load register B with a value of 3
multiply register A and B, saving the result in A
write register A to memory address x

++运算符(及其对应的运算符)需要“某处”进行修改,实质上是任何可以用作左值的东西。

理解C内存模型将有所帮助,因为您将对如何将参数传递给函数以及(最终)如何使用动态内存分配(例如malloc()函数)有一个更好的了解。出于类似的原因,您可能会在某些时候研究一些简单的汇编程序设计,以更好地了解编译器在做什么。另外,如果您使用的是gcc,则-S选项“在编译正常后停止;请勿汇编。” 可能很有趣(尽管我建议您在一个小的代码片段上尝试一下)。

顺便说一句:++指令自1969年就出现了(尽管它始于C的前身B):

(肯·汤普森(Ken Thompson)的观察结果是,++ x的翻译小于x = x + 1的翻译。”

在该维基百科参考之后,您将获得Dennis Ritchie(“ K&R C”中的“ R”)关于C语言历史的有趣文章,此处链接为方便起见:http : //www.bell-labs.com/ usr / dmr / www / chist.html,您可以在其中搜索“ ++”。


6

原因是标准要求操作数是左值。该表达式(a+b)不是左值,因此不允许应用增量运算符。

现在,人们可能会说“好,那确实是原因,但实际上除此以外,没有其他任何真正的原因”,但是不幸的是,有关操作员实际工作方式的特定措辞确实需要这种情况。

表达式++ E等效于(E + = 1)。

显然,E += 1如果E不是左值,则无法编写。真可惜,因为人们可能也曾说过:“将E递增1”就可以了。在那种情况下,完全有可能在非左值上应用运算符,但要使编译器稍微复杂些。

现在,该定义可以改写为小词(我想它甚至不是最初的C,而是B的传家宝),但是这样做将从根本上将语言更改为与先前版本不再兼容的语言。由于可能的收益很小,但潜在的影响却是巨大的,因此从未发生过,而且可能永远不会发生。

如果除C之外还考虑使用C ++(问题被标记为C,但这里讨论了运算符重载),则情况将变得更加复杂。在C中,很难想象会发生这种情况,但是在C ++中,的结果(a+b)很可能是您根本无法增加的结果,或者增加可能会产生非常大的副作用(不仅仅是加1)。编译器必须能够解决这一问题,并在出现问题时进行诊断。在左值上,这仍然是微不足道的检查。对于扔在可怜事物上的括号内的任何形式的偶然表达,并非如此。
这是不是一个真正的原因,它不能 可以完成,但是它肯定可以解释为什么实施此功能的人并没有为添加这样的功能而欣喜若狂,而这种功能几乎不会给很少的人带来什么好处。



3

++尝试将值赋予原始变量,并且由于(a + b)是临时值,因此它无法执行该操作。它们基本上是使编程变得容易的C编程约定的规则。而已。


2

当执行++(a + b)表达式时,例如:

int a, b;
a = 10;
b = 20;
/* NOTE :
 //step 1: expression need to solve first to perform ++ operation over operand
   ++ ( exp );
// in your case 
   ++ ( 10 + 20 );
// step 2: result of that inc by one 
   ++ ( 30 );
// here, you're applying ++ operator over constant value and it's invalid use of ++ operator 
*/
++(a+b);
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.