为什么在C语言中,前置递增运算符会产生rvalue?

31
在C++中,前置自增运算符返回左值,因为自增后的对象本身被返回,而不是其副本。 但在C中,它返回右值。为什么?

1
这是一个非常棒的问题,在我看来 :-) - Prasoon Saurav
https://dev59.com/r3RC5IYBdhLWcg3wOeSB - Jason LeBrun
1
@Jason:那个问题是特定于C++的。 - Prasoon Saurav
该主题链接了许多资源,包括一篇长篇讨论,其中涉及到C语言不按照那种方式工作的原因。 - Jason LeBrun
我不会一行C++代码,但是会C语言的所有内容。 - Mandrake
3个回答

18

C语言没有引用(references)的概念。在C++中,++i会返回对的引用(lvalue),而在C中它将返回的副本(增加后的值)。

C99 6.5.3.1/2:

前缀++操作符的操作数的值递增。结果是递增后操作数的新值。表达式++E等效于(E+=1)。

“表达式的值” <=> 右值

然而,出于历史原因,我认为“C语言中没有引用的概念”可能是一个可能的原因。


1
@HappyMittal:表达式实际上并不返回值,它们具有值(或求值为一个值)和其他属性,如类型和_lvalue-ness_。表达式++i并不“返回”副本,也没有副本。++i求值为i的新值。除非您在生成副本的较大表达式中使用它,否则不会生成副本。 - CB Bailey
@Charles Bailey:好的,我同意++i会计算出i的新值。但我的问题是,在i=j中,i被改变并且表达式产生了一个左值。所以++i即i=i+1与i=j基本相同,那么为什么在这种情况下,表达式不产生左值? - Happy Mittal
1
@Happy:“好的,我同意++i会评估为i的新值”,rvalue表示“表达式的值”(C99)。i=j也会评估为一个值。尝试取地址(i=j),你肯定会得到一个错误。 - Prasoon Saurav
在C语言中,*(++i,&i) = 10;是可行的。你可以定义一个宏#define PREINC(x) *(++(x),&(x))。 (我一个小时前的评论有误,现已删除。) - Aaron McDaid
@PrasoonSaurav,C语言没有引用并不能帮助你的回答。对于int x,在C++中decltype(*&x)int&。但是*&x在C++和C中都是左值,尽管C没有int&。因此,在特殊情况下,C支持表达式外的左值,因此没有引用不是不支持前缀递增的好解释,这是一个薄弱的论点。 - oblitum
显示剩余3条评论

3
C99在第6.3.2.1节的脚注中提到:

名称“lvalue”最初来自赋值表达式E1 = E2,其中要求左操作数E1为(可修改的)左值。它可能更好地被认为代表一个对象的“定位器值”。在这个国际标准中,有时所谓的“rvalue”被描述为“表达式的值”

希望这解释了为什么在C中,++i返回rvalue。


对于C++,我认为这取决于正在递增的对象。如果对象的类型是某个用户定义的类型,则它可能始终返回lvalue。这意味着,如果i的类型如此定义为Index,则您始终可以编写i++++++++++++++i

未定义的行为和序列点重新加载


如果对象的类型不是用户定义的类型,则无论如何,前置递增必须是_lvalue_。 - CB Bailey
我在问关于C语言中的行为,因为在C++中,返回lvalue是有意义的,因为要返回递增后的对象。 - Happy Mittal
1
@Charles:呵呵。是啊,由于OP没有提到对象类型,所以我只回答了问题的一面。 - Nawaz
请查看此评论,其中*&x被称为表达式,并且是一个lvalue:https://dev59.com/questions/i2445IYBdhLWcg3wkLBG#YfUYoYgBc1ULPQZFY4WT - oblitum
很不幸,C标准使用“lvalue”这个术语来指代一个表达式和由该表达式产生的“定位器值”,并且没有将任何术语附加到将前者转换为后者的过程中。如果在i为5时执行int *p = &someAggregateArr[i++].member;,则子表达式someAggregateArr[i++].member应该产生一个“定位器值”,它标识了someAggregateArr[5].member,尽管后者不是代码中出现的任何表达式。 - supercat

2

就我个人而言,我无法想象出使用预增变量作为lvalue可能产生的任何有用语句。在C++中,由于运算符重载的存在,我可以。您是否有具体的例子,说明由于此限制而无法在C中执行某些操作?


@Jason,是的。我无法在C语言中执行此操作 - int *ptr = &++i - Happy Mittal
1
@Filip 这是我(虽然不太严谨)对所提出问题的回答。C++中重载运算符的添加使得前置自增作为左值构造更有价值,因此它被添加了进去。 - Jason LeBrun
+1,我认为这是一个答案,尽管不确定。在C语言中,大多数使用++i的结果作为lvalue的方式都会因缺乏序列点而导致未定义行为。因此,禁止它的语法并没有太大损失,尽管myfunc(&(++i))是可以的,并且有时很有用。还要注意,在C语言中,(++i,i)等同于如果它评估为lvalue的话,++i将是什么,因此即使您需要它,它仍然不是很重要。 - Steve Jessop
由于前缀递增不会产生lvalue,因此以下语句并不起作用:循环递增一个变量:++i %= bufsize。(我承认,你可以用其他方式编写这个语句。) - moooeeeep
@moooeeeep:我希望在C语言中能够看到一些三元复合赋值运算符,但是虽然我可以看到一个存储x=(x+y)&z;的运算符有很多用途,但是在使用z进行掩码处理之前存储中间值并没有太多好处。 - supercat

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接