为什么 a+++++b 不起作用?

92
int main ()
{
   int a = 5,b = 2;
   printf("%d",a+++++b);
   return 0;
}

这段代码会出现以下错误:

error: lvalue required as increment operand

但是,如果我在 a++ +++b 之间加上空格,那么它就可以正常工作。

int main ()
{
   int a = 5,b = 2;
   printf("%d",a++ + ++b);
   return 0;
}

在第一个示例中,这个错误是什么意思?


3
令人惊讶的是,经过这么长时间,竟然没有人发现您所询问的确切表达在C99和C11标准中被用作示例,并且这些标准还给出了很好的解释。我已将其包含在我的回答中。 - Shafik Yaghmour
@ShafikYaghmour——这是C11中的“示例2”§6.4词法元素¶6。它说:“程序片段x+++++y被解析为x ++ ++ + y,这违反了增量运算符的约束条件,尽管解析x ++ + ++ y可能会产生正确的表达式。” - Jonathan Leffler
10个回答

184

编译器是分阶段编写的。第一阶段称为词法分析器,将字符转换为符号结构。因此,“++”变成类似于enum SYMBOL_PLUSPLUS的东西。稍后,解析器阶段将其转换为抽象语法树,但它不能更改符号。您可以通过插入空格(除非它们在引号中)来影响词法分析器。

正常的词法分析器是贪婪的(有一些例外),因此您的代码被解释为

a++ ++ +b

解析器的输入是一个符号流,因此您的代码可能如下所示:
[ SYMBOL_NAME(name = "a"), 
  SYMBOL_PLUS_PLUS, 
  SYMBOL_PLUS_PLUS, 
  SYMBOL_PLUS, 
  SYMBOL_NAME(name = "b") 
]

解析器认为这是语法上不正确的。(根据评论编辑:语义上不正确,因为您不能将++应用于r值,而a++会导致这种情况)

a+++b 

is

a++ +b

这没问题。你的其他例子也都不错。


27
+1 好的解释。不过我必须挑剔一下:它在语法上是正确的,只是有一个语义错误(试图增加 a++ 结果中的左值)。 - user395760
8
a++ 的结果是一个右值。 - Femaref
10
在词法分析器的上下文中,'贪婪'算法通常被称为最大匹配算法(Maximal Munch)。(http://en.wikipedia.org/wiki/Maximal_munch) - JoeG
14
不错。许多语言由于贪婪的词法分析而具有类似的奇怪边角情况。下面是一个非常奇怪的例子,增加表达式的长度会使其更好:在VBScript中,“x = 10&987&&654&&321”是非法的,但令人惊讶的是,“x=10&987&&654&&&321”是合法的。 - Eric Lippert
1
@delnan:rvalue与lvalue 语法限制。你可以仅从语法上确定某个东西是否为rvalue。(在C中,无论如何都是这样的。但在C++中可能会出现问题...) - Luke Maurer
显示剩余6条评论

100

printf("%d",a+++++b); 被解释为 (a++)++ + b,这是最大匹配规则的结果!.

++(后缀形式)不会计算出一个 lvalue,但它要求其操作数为一个 lvalue

! 6.4/4 规定 下一个预处理记号是能够构成预处理记号的最长字符序列"


31
词法分析器使用通常称为“最大匹配”算法来创建令牌。这意味着当词法分析器读取字符时,它会持续读入字符,直到遇到某些不能与已有内容作为同一个标记的字符(例如,如果它一直在读取数字并形成了一个数字,如果遇到一个 A 字符,就知道它不能再继续组成该数字了。因此,它停止并将 A 留在输入缓冲区中,以供下一个标记的开头使用)。然后将该标记返回给解析器。
在这种情况下,这意味着 +++++ 被词法分析为 a ++ ++ + b。由于第一个后缀递增运算符产生右值,所以第二个后缀递增运算符不能应用于它,并且编译器会报错。
顺便说一下,在 C++ 中,您可以重载 operator++ 以产生左值,从而使其有效。例如:
struct bad_code { 
    bad_code &operator++(int) { 
        return *this;
    }
    int operator+(bad_code const &other) { 
        return 1;
    }
};

int main() { 
    bad_code a, b;

    int c = a+++++b;
    return 0;
}

使用我手头的C++编译器(VC ++,g ++,Comeau)进行编译和运行(尽管它什么也没有做)。


1
例如,如果它一直在读取数字,那么它所拥有的就是一个数字,如果它遇到了一个A,它知道那不可能是数字的一部分。16FA是一个完全正常的十六进制数字,其中包含一个A。 - orlp
1
@nightcracker:是的,但如果开头没有0x,它仍然会将其视为16后跟FA,而不是单个十六进制数。 - Jerry Coffin
@Jerry Coffin:你没有说0x不是数字的一部分。 - orlp
@nightcracker: 没有,我没有这样做--鉴于大多数人不认为“x”是数字,似乎这样做非常不必要。 - Jerry Coffin

15
This exact example is covered in the draft C99 standard(same details in C11) section 6.4 Lexical elements paragraph 4 which in says:

If the input stream has been parsed into preprocessing tokens up to a given character, the next preprocessing token is the longest sequence of characters that could constitute a preprocessing token. [...]

which is also known as the maximal munch rule which is used in in lexical analysis to avoid ambiguities and works by taking as many elements as it can to form a valid token.
the paragraph also has two examples the second one is an exact match for you question and is as follows:
例子2:程序片段x+++++y被解析为x ++ ++ + y,这违反了增量运算符的约束条件,即使解析成x ++ + ++ y可能会产生正确的表达式。这告诉我们:
a+++++b

将被解析为:
a ++ ++ + b

这违反了对后置自增的限制,因为第一个后置自增的结果是一个rvalue,而后置自增需要一个lvalue。这在第6.5.2.4后缀递增和递减运算符中有所涵盖,其中写道(重点标记为mine):

后缀递增或递减运算符的操作数应具有限定或未限定的实际或指针类型,并且必须是可修改的lvalue。

后缀++运算符的结果是操作数的值。

这本书 C++ Gotchas 也在 Gotcha #17 中涵盖了这种情况 最大匹配问题,在C++中也是同样的问题,并且它还提供了一些示例。它解释说,在处理以下字符集时:
->*

词法分析器可以执行以下三种操作之一:

  • 将其视为三个标记:->*
  • 将其视为两个标记:->*
  • 将其视为一个标记:->*

最大匹配规则可避免这些歧义。作者指出,在C++上下文中,它:

解决的问题比引起的问题还要多,但在两种常见情况下,它是一种烦恼。

第一个例子是模板,其模板参数也是模板(在C++11中已解决),例如:

list<vector<string>> lovos; // error!
                  ^^

这将尖括号解释为移位运算符,因此需要一个空格来消除歧义:

list< vector<string> > lovos;
                    ^

第二种情况涉及指针的默认参数,例如:
void process( const char *= 0 ); // error!
                         ^^

如果不命名参数,则*=会被解释为赋值运算符,解决方案是在声明时为参数命名。


你知道C++11的哪一部分提到了最大匹配规则吗?2.2.3、2.5.3很有趣,但不如C语言那么明确。>>规则可以在以下链接中找到:https://dev59.com/sGUo5IYBdhLWcg3wxBse - Ciro Santilli OurBigBook.com
好的,谢谢。这是我指出的其中一个部分。明天我的点赞限制解除后我会给你点赞的;-) - Ciro Santilli OurBigBook.com

12

你的编译器试图解析 a+++++b,并将其解释为 (a++)++ +b。现在,后缀递增运算符(a++)的结果不是一个lvalue,也就是说它不能再次被后缀递增。

请不要在生产质量程序中编写这样的代码。考虑一下接手你代码的人需要如何解释你的代码。


10
(a++)++ +b

a++ 返回先前的值,即 a 的 rvalue。您无法对其进行递增操作。


7
因为它会导致未定义的行为。
是哪一个?
c = (a++)++ + b
c = (a) + ++(++b)
c = (a++) + (++b)

是的,你和编译器都不知道它。

编辑:

其他人说的原因是真正的原因:

它被解释为(a++)++ + b

但后增量需要一个lvalue(即具有名称的变量),但(a++)返回一个rvalue,不能递增,因此导致您收到的错误消息。

感谢其他人指出这一点。


5
对于表达式a+++b - (a++) + b和a+(++b),它们的结果是不同的。 - Michael Chinen
4
实际上,后缀++的优先级高于前缀++,因此a+++b始终等同于a++ + b - MByD
4
我认为这不是正确的答案,但我可能错了。我认为词法分析器将其定义为a++ ++ +b,这无法被解析。 - Lou Franco
2
我不同意这个答案。“未定义行为”与标记化歧义非常不同;而且我认为问题也不是这两者之一。 - Jim Blackler
2
否则 a+++++b 会被计算为 ((a++)++)+b。目前从我的角度来看,a+++++b 是可以被解释为 (a++)++)+b。特别是在GCC中,如果插入这些括号并重新编译,错误信息并不会改变。 - Jim Blackler
显示剩余6条评论

5

我认为编译器将其视为

c = ((a++)++)+b

++必须具有可修改的操作数值。a是一个可以被修改的值。a++然而是一个'rvalue',它不能被修改。

顺便说一下,在GCC C上看到的错误是相同的,但措辞不同:需要lvalue作为增量操作数


0
C规范的第6.4节第4段实际上正好涵盖了这种情况:
如果输入流已经被解析为预处理令牌直到给定字符,那么下一个预处理令牌就是能构成预处理令牌的最长字符序列。这个规则有一个例外:头文件名预处理令牌只在#include预处理指令和在#pragma指令的实现定义位置中被识别。在这种情况下,一个既可以是头文件名又可以是字符串字面量的字符序列被识别为前者。 示例1 程序片段1Ex被解析为预处理数字令牌(一个不是有效浮点数或整数常量令牌的令牌),即使将其解析为预处理令牌1和Ex可能会产生一个有效的表达式(例如,如果Ex是一个定义为+1的宏)。类似地,程序片段1E1被解析为预处理数字(一个有效的浮点数常量令牌),无论E是否是宏名。 示例2 程序片段x+++++y被解析为x++ ++ + y,违反了对递增运算符的约束,即使解析x++ + ++ y可能会得到一个正确的表达式。

0

按照以下顺序进行操作:

1.++(前缀自增)

2.+ -(加法或减法)

3."x"+ "y"将两个序列相加

int a = 5,b = 2; printf("%d",a++ + ++b); //a是5,因为它是后缀自增,b是3,因为它是前缀自增 return 0; //结果是5+3=8


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