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

89
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;
}

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

Баршан Дас
источник
3
Удивительно, что спустя столько времени никто не обнаружил, что точное выражение, о котором вы спрашиваете, используется в качестве примера в стандартах C99 и C11. Это также дает хорошее объяснение. Я включил это в свой ответ.
Shafik Yaghmour
@ShafikYaghmour - это «Пример 2» в C11 §6.4 Лексические элементы ¶6 . Он говорит: «Фрагмент программы x+++++yанализируется как x ++ ++ + y, что нарушает ограничение на операторы приращения, даже если синтаксический анализ x ++ + ++ yможет дать правильное выражение».
Джонатан Леффлер

Ответы:

98

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

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

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

Prasoon Saurav
источник
181

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

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

a++ ++ +b

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

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

Что синтаксический анализатор считает синтаксически неверным. (РЕДАКТИРОВАТЬ на основе комментариев: семантически неверно, потому что вы не можете применить ++ к r-значению, что приводит к ++)

a+++b 

является

a++ +b

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

Лу Франко
источник
27
+1 Хорошее объяснение. Я должен придираться: он синтаксически правильный, просто имеет семантическую ошибку (попытка увеличить lvalue в результате a++).
7
a++приводит к rvalue.
Femaref
9
В контексте лексеров «жадный» алгоритм обычно называется Максимальным Мунком ( en.wikipedia.org/wiki/Maximal_munch ).
JoeG
14
Ницца. Во многих языках есть похожие причудливые угловые падежи из-за жадной лексики. Вот действительно странный случай, когда увеличение длины выражения делает его лучше: в VBScript x = 10&987&&654&&321это незаконно, но, как ни странно, x = 10&987&&654&&&321законно.
Эрик Липперт
1
Это не имеет ничего общего с жадностью, а все связано с порядком и приоритетом. ++ выше, чем +, поэтому сначала будут выполнены два ++. +++++ b также будет + ++ ++ b, а не ++ ++ + b. Благодарим @MByD за ссылку.
30

Лексер использует так называемый алгоритм «максимального пережевывания» для создания токенов. Это означает, что пока он читает символы, он продолжает читать символы, пока не встретит что-то, что не может быть частью того же токена, что у него уже есть (например, если он читал цифры, поэтому то, что у него есть, является числом, если он встречает an 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).

Джерри Гроб
источник
1
"например, если он считывает цифры, поэтому у него есть число, если он встречает A, он знает, что не может быть частью числа" 16FA- это прекрасное шестнадцатеричное число , содержащее A.
orlp
1
@nightcracker: да, но без символа 0xв начале он все равно будет обрабатывать это так, как 16следует FA, а не одно шестнадцатеричное число.
Джерри Коффин,
@Jerry Coffin: Вы не сказали, что 0xне входили в число.
orlp
@nightcracker: нет, не считал - учитывая, что большинство людей не считают xцифры, это казалось совершенно ненужным.
Джерри Коффин
14

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

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

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

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

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

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

a+++++b

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

a ++ ++ + b

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

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

а также

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

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

->*

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

  • Рассматривайте это как три лексемы: -, >и*
  • Рассматривайте это как два токена: ->и*
  • Рассматривайте это как один токен: ->*

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

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

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

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

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

list< vector<string> > lovos;
                    ^

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

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

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

Шафик Ягмур
источник
Вы знаете, какая часть C ++ 11 говорит о максимальном правиле жевания? 2.2.3, 2.5.3 интересны, но не так явно, как C. >>Правило задается по адресу: stackoverflow.com/questions/15785496/…
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
1
@CiroSantilli 巴拿馬 文件 六四 事件 法轮功 см. Этот ответ здесь
Шафик Ягмор,
Хорошее спасибо, это один из разделов, на которые я указал. Я проголосую за вас завтра, когда моя кепка снимется ;-)
Чиро Сантилли 郝海东 冠状 病 六四 事件 法轮功
12

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

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

Петер Торок
источник
10
(a++)++ +b

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

Эрик
источник
7

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

Который из них?

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

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

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

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

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

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

Спасибо остальным, что указали на это.

RedX
источник
5
можно сказать то же самое для a +++ b - (a ++) + b и a + (++ b) имеют разные результаты.
Майкл Чинен
4
на самом деле, postfix ++ имеет более высокий приоритет, чем префикс ++, так a+++bчто всегдаa++ + b
MByD
4
Я не думаю, что это правильный ответ, но могу ошибаться. Я думаю, что лексер определяет это как то, a++ ++ +bчто не может быть проанализировано.
Лу Франко
2
Я не согласен с этим ответом. «неопределенное поведение» сильно отличается от неоднозначности токенизации; и я не думаю, что проблема в этом.
Джим Блэклер
2
«В противном случае a +++++ b оценил бы как ((a ++) ++) + b» ... сейчас я считаю, что a+++++b дает оценку (a++)++)+b. Конечно, с GCC, если вы вставите эти скобки и перестроите, сообщение об ошибке не изменится.
Джим Блэклер
5

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

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

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

Кстати ошибка я вижу на 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

ракшит кс
источник