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

Why doesn't a+++++b work?

提问人:Barshan Das 提问时间:3/17/2011 最后编辑:LundinBarshan Das 更新时间:4/3/2020 访问量:10412

问:

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

此代码给出以下错误:

错误:需要 lValue 作为增量操作数

但是,如果我在整个 和 中放置空格,那么它就可以正常工作。a++ +++b

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

第一个示例中的错误是什么意思?

C 左值

评论

3赞 Shafik Yaghmour 7/25/2014
令人惊讶的是,经过这么长时间,没有人发现您所询问的确切表达式在 C99 和 C11 标准中用作示例。它也给出了一个很好的解释。我已经在我的答案中包括了这一点。
0赞 Jonathan Leffler 10/16/2019
@ShafikYaghmour — 这是 C11 §6.4 词法元素 ¶6 中的“示例 2”。它说“程序片段 x+++++y 被解析为 x ++++ + y,这违反了对增量运算符的约束,即使解析 x ++ + ++ y 可能会产生正确的表达式。

答:

10赞 Erik 3/17/2011 #1
(a++)++ +b

a++ 返回上一个值,即 rValue。你不能增加这个。

12赞 Péter Török 3/17/2011 #2

编译器拼命尝试解析 ,并将其解释为 。现在,后递增 () 的结果不是左值,即它不能再次递增。a+++++b(a++)++ +ba++

请不要在生产质量程序中编写此类代码。想想那个可怜的家伙在你身后,需要解释你的代码。

100赞 Prasoon Saurav 3/17/2011 #3

printf("%d",a+++++b);被解释为根据最大蒙克规则(a++)++ + b

++(postfix) 的计算结果不为 an,但它要求其操作数为 .lvaluelvalue

! 6.4/4 说 下一个预处理令牌是可以构成预处理令牌的最长字符序列”

7赞 RedX 4/15/2011 #4

因为它会导致未定义的行为。

是哪一个?

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

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

编辑:

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

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

但是 post increment 需要一个 lValue(这是一个有名称的变量),但 (A++) 返回一个不能递增的 rValue,从而导致您收到错误消息。

感谢其他人指出这一点。

评论

5赞 Michael Chinen 4/15/2011
你可以对 a+++b 说同样的话 - (a++) + b 和 a + (++b) 有不同的结果。
4赞 MByD 4/15/2011
实际上,postfix ++ 的优先级高于 prefix ++,因此总是a+++ba++ + b
4赞 Lou Franco 4/15/2011
我不认为这是正确的答案,但我可能是错的。我认为词法分析器将其定义为无法解析的。a++ ++ +b
2赞 Jim Blackler 4/15/2011
我不同意这个答案。“未定义的行为”与标记化歧义有很大不同;我也不认为问题出在。
2赞 Jim Blackler 4/15/2011
“否则 a+++++b 的计算结果为 ((a++)++)+b” ...我现在的观点是评估为 .当然,对于 GCC,如果您插入这些括号并重新构建,错误消息不会更改。a+++++b(a++)++)+b
184赞 Lou Franco 4/15/2011 #5

编译器是分阶段编写的。第一阶段称为词法分析器,将字符转换为符号结构。所以“++”变成了一个类似的东西。稍后,解析器阶段将其转换为抽象语法树,但它无法更改符号。您可以通过插入空格(结束符号,除非它们在引号中)来影响词法分析器。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 

a++ +b

这没关系。你的其他例子也是如此。

评论

27赞 4/15/2011
+1 很好的解释。不过,我必须吹毛求疵:它在语法上是正确的,它只是有一个语义错误(试图增加由)。a++
8赞 Femaref 4/15/2011
a++导致右值。
10赞 JoeG 4/15/2011
在词法分析器的上下文中,“贪婪”算法通常称为最大蒙克(en.wikipedia.org/wiki/Maximal_munch)。
14赞 Eric Lippert 4/16/2011
好。由于贪婪的词法,许多语言都有类似的奇怪的极端情况。这是一个非常奇怪的问题,使表达式更长会使其更好:在 VBScript 中是非法的,但奇怪的是,它是合法的。x = 10&987&&654&&321x = 10&987&&654&&&321
1赞 4/16/2011
它与贪婪无关,而与秩序和优先权有关。++ 高于 +,因此将首先完成两个 ++。+++++b 也将是 + ++ ++ b,而不是 ++ ++ + b。感谢@MByD的链接。
5赞 Jim Blackler 4/15/2011 #6

我认为编译器将其视为

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

++必须具有可修改的值作为操作数。a 是可以修改的值。 但是是“rValue”,则无法修改。a++

顺便说一句,我在 GCC C 上看到的错误是相同的,但措辞不同:.lvalue required as increment operand

31赞 Jerry Coffin 4/15/2011 #7

词法分析器使用通常称为“最大咀嚼”算法来创建标记。这意味着当它读取字符时,它会继续读取字符,直到它遇到不能与它已经拥有的标记相同的标记的一部分的东西(例如,如果它一直在读取数字,那么它所拥有的是一个数字,如果它遇到一个,它知道它不能是数字的一部分。 所以它会停止并留在输入缓冲区中用作下一个标记的开头)。然后,它将该令牌返回给分析器。AA

在本例中,这意味着被词法化为 .由于第一个后增量产生一个 rvalue,因此第二个不能应用于它,并且编译器会给出错误。+++++a ++ ++ + b

只是 FWIW,在 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赞 orlp 4/16/2011
“例如,如果它一直在读取数字,那么它所拥有的是一个数字,如果它遇到一个 A,它就知道它不能是数字的一部分”是一个包含 A 的完全精细的十六进制16FA
1赞 Jerry Coffin 4/16/2011
@nightcracker:是的,但是如果开头没有 a,它仍然会将其视为后跟 ,而不是单个十六进制数。0x16FA
0赞 orlp 4/16/2011
@Jerry棺材:你没说不是数字的一部分。0x
0赞 Jerry Coffin 4/16/2011
@nightcracker:不,我没有——考虑到大多数人不考虑数字,这似乎完全没有必要。x
15赞 Shafik Yaghmour 7/25/2014 #8

C99 标准草案与 C11 中的细节相同)第 6.4 节词汇要素第 4 段中涵盖了这个确切的例子,其中说:

如果输入流已解析为预处理令牌,则最多 给定字符,下一个预处理标记是最长的序列 可以构成预处理令牌的字符。[...]

这也被称为最大蒙克规则,用于词汇分析以避免歧义,并通过获取尽可能多的元素来形成有效的标记。

该段落还有两个示例,第二个示例与您的问题完全匹配,如下所示:

示例 2 程序片段 x+++++y 被解析为 x ++++ + y,其中 违反对增量运算符的约束,即使解析 x ++ + ++ y 可能会产生正确的表达式。

它告诉我们:

a+++++b

将被解析为:

a ++ ++ + b

这违反了对后递增的约束,因为第一个后递增的结果是右值,而后递增量需要左值。这在后缀递增和递减运算符一节中有所介绍,其中说(强调我的):6.5.2.4

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

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

C++ Gotchas一书在Maximal Munch Problems中也介绍了这种情况,在C++中也是同样的问题,并且还给出了一些示例。它解释了在处理以下字符集时:Gotcha #17

->*

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

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

最大咀嚼规则允许它避免这些歧义。作者指出,它(在 C++ 上下文中):

解决的问题比它引起的要多得多,但在两个共同点上 情况,这很烦人。

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

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

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

list< vector<string> > lovos;
                    ^

第二种情况涉及指针的默认参数,例如:

void process( const char *= 0 ); // error!
                         ^^

将被解释为赋值运算符,在这种情况下,解决方案是在声明中命名参数。*=

评论

0赞 Ciro Santilli OurBigBook.com 5/9/2016
你知道 C++11 的哪一部分说了最大咀嚼规则吗?2.2.3、2.5.3 很有趣,但不像 C 语言那么明确。该规则在以下位置询问:stackoverflow.com/questions/15785496/...>>
1赞 Shafik Yaghmour 5/10/2016
@CiroSantilli巴拿馬文件六四事件法轮功 see this answer here
0赞 Ciro Santilli OurBigBook.com 5/10/2016
非常感谢,这是我指出的部分之一。明天当我的帽子脱落时,我会给你投赞成票;-)
0赞 rakshit ks 4/3/2020 #9

遵循此优先顺序

1.++(预增量)

2.+ -(加法或减法)

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

int a = 5,b = 2; printf("%d",a++ + ++b); //a is 5 since it is post increment b is 3 pre increment return 0; //it is 5+3=8

0赞 Chris Dodd 11/23/2023 #10

C 规范的第 6.4 节第 4 段实际上正好涵盖了这种情况:

如果输入流已解析为预处理标记,直到给定字符,则下一个 预处理令牌是可以构成预处理的最长字符序列 令 牌。此规则有一个例外:仅识别标头名称预处理令牌 在 #include 预处理指令中以及在 #pragma 内实现定义的位置 指令。在此类上下文中,可以是标头名称或字符串的字符序列 literal 被识别为前者。
示例 1 程序片段 1Ex 被解析为预处理数字标记(不是有效的浮点数或整数标记 常量令牌),即使作为预处理令牌 1 和 Ex 对的解析可能会生成有效的表达式(对于 例如,如果 Ex 是定义为 +1 的宏。类似地,程序片段 1E1 被解析为预处理数字(1 这是一个有效的浮动常量标记),无论 E 是否为宏名称。
示例 2 程序片段 x+++++y 被解析为 x ++ ++ + y ,这违反了对增量运算符的约束, 即使解析 x ++ + ++ y 可能会产生正确的表达式