Почему не работает +++++ b? - PullRequest
84 голосов
/ 17 марта 2011
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;
}

Что означает ошибка в первом примере?

Ответы [ 8 ]

178 голосов
/ 15 апреля 2011

Компиляторы написаны поэтапно. Первый этап называется лексером и превращает персонажей в символическую структуру. Так что «++» становится чем-то вроде 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

Что нормально. Как и другие ваши примеры.

92 голосов
/ 17 марта 2011

printf("%d",a+++++b); интерпретируется как (a++)++ + b в соответствии с правилом максимального жаворонка ! .

++ (постфикс) не оценивается как lvalue, но требует, чтобы его операнд был lvalue.

! 6.4 / 4 говорит следующий токен предварительной обработки - это самая длинная последовательность символов, которая может составлять токен предварительной обработки »

30 голосов
/ 15 апреля 2011

Лексер использует то, что обычно называют алгоритмом "максимального жаворонка", для создания токенов.Это означает, что при чтении символов он продолжает читать символы до тех пор, пока не встретит что-то, что не может быть частью того же самого токена, что и у него (например, если он читает цифры, то есть число, если он встречается).A, он знает, что не может быть частью числа, поэтому останавливается и оставляет A во входном буфере для использования в качестве начала следующего токена).Затем он возвращает этот токен анализатору.

В этом случае это означает, что +++++ становится лексированным как a ++ ++ + b.Так как первый постинкремент выдает значение r, второе не может быть применено к нему, и компилятор выдает ошибку.

Просто FWIW, в C ++ вы можете перегрузить operator++, чтобы получить lvalue, чтопозволяет это работать.Например:

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).

14 голосов
/ 25 июля 2014

Этот точный пример описан в черновом стандарте C99 ( те же подробности в C11 ) 6.4 Лексические элементы, параграф 4 , в котором говорится:

Если входной поток был проанализирован в токены предварительной обработки до заданного символа, следующий токен предварительной обработки - это самая длинная последовательность символов, которая может составлять токен предварительной обработки.[...]

, также известное как правило максимального жаворонка , которое используется в лексическом анализе, чтобы избежать неясностей и работает, беря столько элементов, сколько возможно, чтобысформируйте действительный токен.

параграф также имеет два примера, второй является точным соответствием вашему вопросу и выглядит следующим образом:

ПРИМЕР 2 Фрагмент программы x +++++ y анализируется как x ++ ++ + y, что нарушает ограничение на операторы приращения, даже если синтаксический анализ x ++ + ++ y может привести к правильному выражению.

, который сообщаетнам, что:

a+++++b

будет проанализирован как:

a ++ ++ + b

, что нарушает ограничения на пост-инкремент, так как результатом первого пост-инкремента является rvalue, а пост-инкремент требует lvalue,Это описано в разделе 6.5.2.4 Операторы приращения и уменьшения постфикса , в которых говорится ( выделение шахты ):

Операнд оператора увеличения или уменьшения постфиксадолжен иметь квалифицированный или неквалифицированный вещественный или указательный тип, а должен быть изменяемым lvalue.

и

Результат оператора postfix ++ равензначение операнда.

Книга C ++ Gotchas также охватывает этот случай в Gotcha #17 Максимальные проблемы Мунка такая же проблема в C ++ , а также приводит несколько примеров.Это объясняет, что при работе со следующим набором символов:

->*

лексический анализатор может выполнять одно из трех действий:

  • Обрабатывать его как три токена: -,> и *
  • Считайте его двумя токенами: -> и *
  • Считайте его одним токеном: ->*

Правило maxim munch позволяет избежать этих двусмысленностей.Автор указывает, что он ( В контексте C ++ ):

решает гораздо больше проблем, чем вызывает, но в двух распространенных ситуациях это раздражает.

Первым примером будут шаблоны, аргументы шаблона которых также являются шаблонами (, который был решен в C ++ 11 ), например:

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

Что интерпретирует закрывающие угловые скобки как оператор сдвига , и поэтому для устранения неоднозначности требуется пробел:

list< vector<string> > lovos;
                    ^

Во втором случае используются аргументы по умолчанию для указателей, например:

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

будет интерпретироваться как *= оператор присваивания, решение в этом случае заключается в именовании параметров в объявлении.

12 голосов
/ 17 марта 2011

Ваш компилятор отчаянно пытается проанализировать a+++++b и интерпретирует его как (a++)++ +b. Теперь результат постинкремента (a++) не является lvalue , то есть он не может быть снова постинкрементным.

Пожалуйста, никогда не пишите такой код в программах производственного качества. Подумайте о бедняге, идущем за вами, который должен интерпретировать ваш код.

10 голосов
/ 17 марта 2011
(a++)++ +b

a ++ возвращает предыдущее значение, r-значение. Вы не можете увеличить это.

7 голосов
/ 15 апреля 2011

Потому что это вызывает неопределенное поведение.

Какой это?

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

Да, ни вы, ни компилятор это не знают.

РЕДАКТИРОВАТЬ:

Истинная причина, как говорят другие:

Это интерпретируется как (a++)++ + b.

, нопост-инкремент требует lvalue (который является переменной с именем), но (a ++) возвращает rvalue, которое не может быть увеличено, что приводит к полученному сообщению об ошибке.

Спасибо остальным за указание на это.1019 *

5 голосов
/ 15 апреля 2011

Я думаю, что компилятор видит это как

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

++ должен иметь в качестве операнда значение, которое можно изменить.а это значение, которое можно изменить.a++ однако это 'rvalue', его нельзя изменить.

Кстати, ошибка, которую я вижу в GCC C, та же, но по-другому: lvalue required as increment operand.

...