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


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

此代码给出以下错误:

错误:需要左值作为增量操作数

但是,如果我在a++ +和之间放置空格++b,则效果很好。

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

该错误在第一个示例中意味着什么?


3
令人惊讶的是,所有这些时间之后,没有人发现您要询问的确切表达式被用作C99和C11标准的示例。它也给出了很好的解释。我已将其包含在我的答案中。
Shafik Yaghmour 2014年

@ShafikYaghmour —这是C11§6.4词汇元素¶6中的“示例2” 。它说:“程序片段x+++++y被解析为x ++ ++ + y,这违反了对增量运算符的约束,即使该解析x ++ + ++ y可能产生正确的表达式也是如此。”
乔纳森·勒夫勒

Answers:


97

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

++(后缀)不lvalue等于,但要求其操作数为lvalue

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


181

编译器是分阶段编写的。第一阶段称为词法分析器,它将字符转换为符号结构。所以“ ++”变成了类似enum SYMBOL_PLUSPLUS。稍后,解析器阶段将其转变为抽象语法树,但是它无法更改符号。您可以通过插入空格(除非符号用引号引起来,否则空格)来影响词法分析器。

普通词法编辑器是贪婪的(有一些例外),因此您的代码被解释为

a++ ++ +b

解析器的输入是符号流,因此您的代码将类似于:

[ SYMBOL_NAME(name = "a"), 
  SYMBOL_PLUS_PLUS, 
  SYMBOL_PLUS_PLUS, 
  SYMBOL_PLUS, 
  SYMBOL_NAME(name = "b") 
]

解析器认为在语法上是不正确的。(根据注释进行编辑:从语义上讲是不正确的,因为您不能将++应用于a值导致的r值)

a+++b 

a++ +b

没关系 您的其他示例也是如此。


27
+1很好的解释。不过,我必须要挑剔:从语法上讲这是正确的,它只是存在语义错误(试图增加由产生的左值a++)。

7
a++导致右值。
Femaref 2011年

9
在词法分析器的上下文中,“贪婪”算法通常称为Maximal Munch(en.wikipedia.org/wiki/Maximal_munch)。
JoeG 2011年

14
真好 由于贪婪的词汇,许多语言都有类似的异常情况。这是一个很奇怪的地方,使表达式更长可以使其变得更好:在VBScript中x = 10&987&&654&&321是非法的,但奇怪的x = 10&987&&654&&&321是合法的。
埃里克·利珀特

1
它与贪婪无关,而与秩序和优先顺序有关。++比+高,因此首先要完成两个++。+++++ b也将是+++++ b,而不是+++++ b。将该链接归功于@MByD。

30

词法分析器使用通常称为“最大数量”的算法来创建令牌。这意味着在读取字符时,它将一直读取字符,直到遇到无法与已有标记相同的标记(例如,如果一直在读取数字,那么它就是一个数字,如果遇到了) an A,它知道不能是数字的一部分,所以它停止并离开A在输入缓冲区中,以用作下一个标记的开头)。然后,它将令牌返回给解析器。

在这种情况下,这意味着+++++将词法化为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
“例如,如果它已经阅读位,因此它有什么是一个数字,如果遇到A,它知道不可能是号码的一部分” 16FA是一个完全正常的十六进制包含一个A.
orlp

1
@nightcracker:是的,但是0x在开始时不带a ,它仍然将16其后跟FA,而不是一个十六进制数。
杰里·科芬

@杰里·科芬:你不是说0x不是数字的一部分。
orlp 2011年

@nightcracker:不,我没有-鉴于大多数人不考虑x数字,这似乎完全没有必要。
杰里·科芬

14

这个确切的例子在C99标准草案C11中的相同细节)第6.4Lexical elements第4段中有所说明,其中说:

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

它也被称为最大割礼规则,在词法分析中用于避免歧义,并且通过采用尽可能多的元素来形成有效标记而起作用。

该段还有两个示例,第二个示例完全符合您的问题,如下所示:

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

告诉我们:

a+++++b

将被解析为:

a ++ ++ + b

这违反了对职位增量的限制,因为第一个职位增量的结果是一个右值,而职位增量需要一个左值。这在6.5.2.4 Postfix增量和减量运算符部分中进行了介绍,其中(强调):

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

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

C ++ Gotchas》一书在“ Gotcha #17 最大蒙克问题”中也涉及了这种情况,在C ++中也是同样的问题,并给出了一些示例。它说明了在处理以下字符集时的问题:

->*

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

  • 把它当作三个令牌:->*
  • 将其视为两个令牌:->*
  • 将其视为一个令牌: ->*

最大适合规则允许,以避免这些模糊。作者指出了这一点(在C ++上下文中):

解决的问题多于其所能解决的问题,但是在两种常见情况下,这很烦人。

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

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

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

list< vector<string> > lovos;
                    ^

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

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

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


您知道C ++ 11的哪一部分说明最大咀嚼规则吗?2.2.3,2.5.3是有趣的,但不是作为明确的C. >>规则是在问:stackoverflow.com/questions/15785496/...
西罗桑蒂利郝海东冠状病六四事件法轮功

1
@CiroSantilli巴拿马文件六四事件法轮功在这里
Shafik Yaghmour

很好,谢谢,这是我指向的部分之一。我会给予好评明天你当我的帽子戴过;-)
西罗桑蒂利郝海东冠状病六四事件法轮功

12

您的编译器拼命尝试解析a+++++b,并将其解释为(a++)++ +b。现在,post-increment(a++)的结果不是左值,即不能再次进行post-increment。

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


10
(a++)++ +b

a ++返回前一个值,即右值。您不能增加它。


7

因为它会导致不确定的行为。

哪一个?

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

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

编辑:

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

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

但是后递增需要一个左值(这是一个带名称的变量),但是(a ++)返回一个右值,该右值不能递增,从而导致出现错误消息。

谢谢其他人指出这一点。


5
对于a +++ b,您可以说相同--(a ++)+ b和a +(++ b)具有不同的结果。
Michael Chinen

4
实际上,后缀++的优先级高于前缀++,因此a+++b始终如此a++ + b
MByD 2011年

4
我认为这不是正确的答案,但我可能是错的。我认为词法分析器将其定义为a++ ++ +b无法解析的。
Lou Franco

2
我不同意这个答案。“未定义的行为”与标记化的歧义有很大不同;我也不是问题所在。
Jim Blackler

2
“否则a +++++ b的评估结果为((a ++)++)+ b” ...我现在的观点a+++++b 确实是评估为(a++)++)+b。当然,对于GCC,如果您插入这些括号并进行重建,则错误消息不会更改。
Jim Blackler

5

我认为编译器将其视为

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

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

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


0

遵循此先行顺序

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

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.