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

此代码给出以下错误:

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

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

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

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

4

9 回答 9

182

编译器是分阶段编写的。第一阶段称为词法分析器,将字符转换为符号结构。所以“++”变成了类似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

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

于 2011-04-15T13:16:15.043 回答
98

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

++(postfix) 不计算为 anlvalue但它要求其操作数为lvalue.

!6.4/4 表示下一个预处理标记是可以构成预处理标记的最长字符序列”

于 2011-03-17T15:40:15.000 回答
30

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

在这种情况下,这意味着+++++被 lexed 为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)编译和运行(尽管它什么都不做)。

于 2011-04-15T14:07:16.007 回答
14

这个确切的例子包含在C99 标准草案C11 中的相同细节)第6.4 节词法元素第 4 段中,其中说:

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

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

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

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

这告诉我们:

a+++++b

将被解析为:

a ++ ++ + b

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

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

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

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

->*

词法分析器可以做以下三件事之一:

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

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

解决的问题多于导致的问题,但在两种常见情况下,这很烦人。

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

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

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

list< vector<string> > lovos;
                    ^

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

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

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

于 2014-07-25T04:12:02.023 回答
12

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

请永远不要在生产质量程序中编写这样的代码。想想那个需要解释你的代码的可怜的家伙。

于 2011-03-17T15:28:57.800 回答
10
(a++)++ +b

a++ 返回前一个值,一个右值。你不能增加这个。

于 2011-03-17T15:27:36.317 回答
7

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

哪一个?

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

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

编辑:

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

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

但是后增量需要一个左值(这是一个有名称的变量),但是 (a++) 返回一个无法递增的右值,从而导致您收到错误消息。

感谢其他人指出这一点。

于 2011-04-15T13:11:47.047 回答
5

我认为编译器将其视为

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

++必须有一个可以修改的值作为操作数。a 是可以修改的值。a++然而是一个“右值”,它不能被修改。

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

于 2011-04-15T13:16:18.630 回答
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

于 2020-04-03T11:49:47.867 回答