Почему не работает +++++ b?

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

Этот код дает следующую ошибку:

error: lvalue, требуемое как операнд приращения

Но если я помещаю пробелы на протяжении a++ + и ++b, то он отлично работает.

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

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

Ответ 1

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

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

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

Ответ 2

Составители написаны поэтапно. Первый этап называется лексером и превращает символы в символическую структуру. Поэтому "++" становится чем-то вроде enum SYMBOL_PLUSPLUS. Позже сценарий парсера превращает это в абстрактное синтаксическое дерево, но он не может изменить символы. Вы можете повлиять на лексер, вставив пробелы (которые заканчивают символы, если они не указаны в кавычках).

Нормальные лексеры жадные (за некоторыми исключениями), поэтому ваш код интерпретируется как

a++ ++ +b

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

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

То, что считает синтаксический анализатор, синтаксически неверно. (EDIT на основе комментариев: семантически некорректно, потому что вы не можете применить ++ к r-значению, которое дает результат ++)

a+++b 

есть

a++ +b

Это нормально. Вот и другие примеры.

Ответ 3

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

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

Просто FWIW, на С++ вы можете перегрузить 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;
}

Скомпилирует и запускает (хотя и ничего не делает) с компиляторами С++, которые мне удобны (VС++, g++, Comeau).

Ответ 4

Этот точный пример описан в разделе 6.4 проекта стандарта C99 (те же подробности в C11). Пункт 4 лексических элементов гласит:

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

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

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

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

что говорит нам о том, что:

a+++++b

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

a ++ ++ + b

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

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

а также

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

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

->*

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

  • Рассматривайте это как три жетона: -, > и *
  • Считайте это двумя жетонами: -> и *
  • Считайте это одним знаком: ->*

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

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

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

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

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

list< vector<string> > lovos;
                    ^

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

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

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

Ответ 5

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

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

Ответ 6

(a++)++ +b

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

Ответ 7

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

Какая из них?

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

Да, ни вы, ни компилятор этого не знаете. Забастовкa >

EDIT:

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

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

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

спасибо другим, чтобы указать на это.

Ответ 8

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

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

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

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