为啥 c = ++(a+b) 会出现编译错误?
Posted
技术标签:
【中文标题】为啥 c = ++(a+b) 会出现编译错误?【英文标题】:Why does c = ++(a+b) give compilation error?为什么 c = ++(a+b) 会出现编译错误? 【发布时间】:2018-11-29 18:23:59 【问题描述】:经过研究,我读到增量运算符要求操作数具有可修改的数据对象:https://en.wikipedia.org/wiki/Increment_and_decrement_operators。
据此我猜它会给出编译错误,因为(a+b)
是一个临时整数,因此不可修改。
这种理解正确吗?这是我第一次尝试研究问题,所以如果有我应该寻找的东西,请提出建议。
【问题讨论】:
这在研究方面还不错。你在正确的轨道上。 你希望表达式做什么? 根据 C11 标准 6.5.3.1:前缀递增或递减运算符的操作数应具有原子、合格或不合格的实数或指针类型,并且应为可修改的左值 您希望 1 如何在 a 和 b 之间分配? “数组索引应该从 0 还是 1 开始?我对 0.5 的妥协被拒绝了,我认为没有适当的考虑。” — Stan Kelly-Bootle 我认为一个后续问题是,当c = a + b + 1
使您的意图更清晰并且打字时间也更短时,您为什么要这样做。递增/递减运算符做两件事 1. 它们和它们的参数形成一个表达式(可以使用,例如在 for 循环中),2. 它们修改参数。在您的示例中,您使用的是属性 1. 但不是属性 2.,因为您丢弃了修改后的参数。如果您不需要属性 2. 并且只想要表达式,那么您可以只写一个表达式,例如x+1 而不是 x++。
【参考方案1】:
你是对的。 ++
尝试将新值分配给原始变量。所以++a
将取a
的值,将1
添加到它,然后将其分配回a
。因为,正如你所说, (a+b) 是一个临时值,而不是一个分配了内存地址的变量,所以无法执行分配。
【讨论】:
【参考方案2】:这只是一个规则,仅此而已,并且可能是为了 (1) 使编写 C 编译器更容易和 (2) 没有人说服 C 标准委员会放宽它。
通俗地说,如果foo
可以出现在像foo = bar
这样的赋值表达式的左侧,你只能写++foo
。既然不能写a + b = bar
,那你也不能写++(a + b)
。
a + b
没有真正的理由不能产生 ++
可以操作的临时值,其结果就是表达式 ++(a + b)
的值。
【讨论】:
我认为第 (1) 点一针见血。仅仅看一下 C++ 中临时实现的规则就会让人反胃(但它很强大,但不得不说)。 @StoryTeller:确实,与我们钟爱的语言 C++ 不同,C 仍然相对简单地编译成汇编。 恕我直言,这是一个真正的原因:如果++
有时会产生修改某些东西的副作用,而有时却没有,那将是一个可怕的混乱。
@dng:确实如此;这就是引入术语 lvalue 和 rvalue 的原因,尽管事情比现在更复杂(特别是在 C++ 中)。例如,一个常量永远不可能是一个左值:像 5 = a 这样的东西是没有意义的。
@Bathsheba 这就解释了为什么 5++ 也会导致编译错误【参考方案3】:
C11 标准在第 6.5.3.1 节中声明
前缀递增或递减运算符的操作数应具有 原子的、合格的或不合格的实数或指针类型,并且应为 可修改的左值
并且“可修改的左值”在第 6.3.2.1 小节 1 中进行了描述
左值是一个表达式(对象类型不是 void) 潜在地指定一个对象;如果左值不指定 评估对象时,行为未定义。当一个 据说对象具有特定类型,该类型是 由用于指定对象的左值指定。 一个可修改的 lvalue 是一个没有数组类型的左值,没有 不完整的类型,没有 const 限定的类型,并且 如果是结构或联合,则没有任何成员 (包括,递归地,所有包含的任何成员或元素 聚合或联合)具有 const 限定类型。
所以(a+b)
不是可修改的左值,因此不符合前缀增量运算符的条件。
【讨论】:
您从这些定义中得出的结论缺失...您想说 (a+b) 没有潜在地指定对象,但这些段落不允许这样做。【参考方案4】:我认为您主要回答了自己的问题。 正如 C.Gibbons 所提到的,我可能会对您的措辞进行一些小改动,并将“临时变量”替换为“右值”。
随着您了解 C 的内存模型,术语变量、参数、临时变量等将变得更加清晰(这看起来是一个很好的概述:https://www.geeksforgeeks.org/memory-layout-of-c-program/)。
当您刚开始时,术语“右值”可能看起来不透明,所以我希望以下内容有助于您对它的直觉。
左值/右值是指等号的不同边(赋值运算符): lvalue = 左侧(小写 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 选项“在适当的编译阶段后停止;不要汇编。”可能很有趣(尽管我建议在 small 代码片段上尝试一下)。
顺便说一句: ++ 指令has been around since 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 您可以在其中搜索“ ++"。
【讨论】:
【参考方案5】:原因是标准要求操作数是左值。表达式(a+b)
不是左值,因此不允许应用自增运算符。
现在,有人可能会说“好吧,确实是这个原因,但实际上没有*真正*的原因除此之外”,但不幸的是,操作员如何工作的特定措辞实际上是 确实要求是这样的。
表达式 ++E 等价于 (E+=1)。
显然,如果 E
不是左值,则不能编写 E += 1
。这是一种耻辱,因为人们也可以说:“将 E 增加一个” 并完成。在这种情况下,(原则上)将运算符应用于非左值是完全可能的,但代价是编译器稍微复杂一些。
现在,定义可以简单地改写(我认为它甚至不是最初的 C,而是 B 的传家宝),但这样做会从根本上改变语言,使其不再与其以前的版本兼容。由于可能的好处很小,但可能的影响很大,所以这从未发生过,也可能永远不会发生。
如果除了 C 之外还考虑 C++(问题标记为 C,但讨论过运算符重载),情况会变得更加复杂。在 C 中,很难想象会出现这种情况,但在 C++ 中,(a+b)
的结果很可能是您根本无法增加的东西,或者增加可能会产生非常大的副作用(不仅仅是加 1)。编译器必须能够应对这种情况,并在出现问题时对其进行诊断。在左值上,检查起来仍然很简单。括号内的任何随意表达都不是这样,你扔给可怜的东西。
这不是 真正的原因为什么它不能完成,但它确实可以解释为什么实现它的人并不完全欣喜若狂地添加这样一个对极少数人几乎没有好处的功能。
【讨论】:
【参考方案6】:当执行 ++(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);
【讨论】:
【参考方案7】:(a+b) 计算为一个不能递增的右值。
【讨论】:
【参考方案8】:++ 尝试将值赋予原始变量,并且由于 (a+b) 是临时值,因此它无法执行操作。它们基本上是 C 编程约定的规则,使编程变得容易。而已。
【讨论】:
以上是关于为啥 c = ++(a+b) 会出现编译错误?的主要内容,如果未能解决你的问题,请参考以下文章