Почему не работает +++++b?
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;
}
Что означает ошибка в первом примере?
10 ответов
printf("%d",a+++++b);
интерпретируется как (a++)++ + b
согласно правилу Максимального Мунка !,
++
(postfix) не оценивает lvalue
но он требует, чтобы его операнд был lvalue
,
! 6.4 / 4 говорится, что следующим токеном предварительной обработки является самая длинная последовательность символов, которая может составлять токен предварительной обработки "
Компиляторы написаны поэтапно. Первый этап называется лексером и превращает персонажей в символическую структуру. Так что "++" становится чем-то вроде 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
Что в порядке. Как и ваши другие примеры.
Для создания токенов лексер использует алгоритм, который обычно называют "максимальным жеванием". Это означает, что при чтении символов он продолжает читать символы до тех пор, пока не встретит что-то, что не может быть частью того же токена, что и у него (например, если он читает цифры, то есть число, если он встречается). 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).
Точный пример описан в разделе проекта стандарта 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++, а также приводятся некоторые примеры. Это объясняет это при работе со следующим набором символов:
->*
лексический анализатор может выполнять одно из трех действий:
- Рассматривайте это как три жетона:
-
,>
а также*
- Считайте это двумя жетонами:
->
а также*
- Считайте это одним знаком:
->*
Правило максимального жаворонка позволяет избежать этих двусмысленностей. Автор указывает, что это (в контексте C++):
решает гораздо больше проблем, чем вызывает, но в двух распространенных ситуациях это раздражает.
Первый пример - это шаблоны, аргументы которых также являются шаблонами (что было решено в C++ 11), например:
list<vector<string>> lovos; // error!
^^
Что интерпретирует закрывающие угловые скобки как оператор сдвига, поэтому для устранения неоднозначности требуется пробел:
list< vector<string> > lovos;
^
Во втором случае используются аргументы по умолчанию для указателей, например:
void process( const char *= 0 ); // error!
^^
будет интерпретироваться как *=
Оператор присваивания, решение в этом случае заключается в названии параметров в объявлении.
Ваш компилятор отчаянно пытается разобрать a+++++b
и интерпретирует это как (a++)++ +b
, Теперь результат постинкремента (a++
) не является lvalue, то есть не может быть снова увеличено после.
Пожалуйста, никогда не пишите такой код в программах качества производства. Подумайте о бедняге, который придет за вами, который должен интерпретировать ваш код
(a++)++ +b
a++ возвращает предыдущее значение - значение. Вы не можете увеличить это.
Потому что это вызывает неопределенное поведение.
Который из них?
c = (a++)++ + b
c = (a) + ++(++b)
c = (a++) + (++b)
Да, ни вы, ни компилятор не знают этого.
РЕДАКТИРОВАТЬ:
Настоящая причина - та, о которой говорили другие:
Это интерпретируется как (a++)++ + b
,
но постинкремент требует lvalue (который является переменной с именем), но (a++) возвращает значение r, которое не может быть увеличено, что приводит к полученному вами сообщению об ошибке.
Спасибо остальным за указание на это.
Я думаю, что компилятор видит это как
c = ((a++)++)+b
++
должен иметь в качестве операнда значение, которое можно изменить. а это значение, которое можно изменить. a++
однако это "значение", оно не может быть изменено.
Кстати, ошибка, которую я вижу в GCC C, такая же, но по-другому: lvalue required as increment operand
,
Следуйте этому порядку прецедента
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
Параграф 4 раздела 6.4 спецификации C фактически охватывает именно этот случай:
Если входной поток был разобран на токены предварительной обработки до заданного символа, следующий токен предварительной обработки представляет собой самую длинную последовательность символов, которая может составлять токен предварительной обработки. Из этого правила есть одно исключение: токены предварительной обработки имени заголовка распознаются только в директивах предварительной обработки #include и в местах, определенных реализацией в директивах #pragma. В таких контекстах последовательность символов, которая может быть либо именем заголовка, либо строковым литералом, распознается как первая.
ПРИМЕР 1. Фрагмент программы 1Ex анализируется как числовой токен предварительной обработки (не являющийся допустимым токеном с плавающей запятой или целочисленной константой), даже несмотря на то, что анализ пары токенов предварительной обработки 1 и Ex может дать допустимое выражение (например, если Ex был макросом, определенным как +1). Аналогично, фрагмент программы 1E1 анализируется как номер предварительной обработки (тот, который является допустимым токеном с плавающей константой), независимо от того, является ли E именем макроса.
ПРИМЕР 2. Фрагмент программы x++++y анализируется как x ++ ++ + y , что нарушает ограничение на операторы приращения, даже несмотря на то, что анализ x ++ + ++ y может дать правильное выражение.