Чому не працює +++++ b?


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

Цей код видає таку помилку:

помилка: потрібне значення l як операнд збільшення

Але якщо я розставляю пробіли a++ +і ++b, то це чудово працює.

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

Що означає помилка в першому прикладі?


3
Після всього цього часу дивно, що ніхто не виявив, що саме той вираз, про який ви питаєте, використовується як приклад у стандартах C99 та C11. Це також дає гарне пояснення. Я включив це у свою відповідь.
Шафік Ягмор

@ShafikYaghmour - це "Приклад 2" у C11 §6.4 Лексичні елементи №6 . У ньому сказано: "Фрагмент програми x+++++yаналізується як x ++ ++ + y, що порушує обмеження на оператори збільшення, навіть якщо аналіз x ++ + ++ yможе дати правильний вираз".
Джонатан Леффлер,

Відповіді:


98

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

++(postfix) не обчислює значення, lvalueале його операндом потрібно бути lvalue.

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


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 Гарне пояснення. Мені, однак, потрібно запустити nitpick: це синтаксично правильно, у ньому просто є семантична помилка (спроба збільшити значення lvalue в результаті a++).

7
a++призводить до значення r.
Femaref

9
У контексті лексів `` жадібний '' алгоритм зазвичай називають Maximal Munch ( 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

що порушує обмеження на приріст повідомлення, оскільки результат першого збільшення посту є значенням r, а для збільшення розміру потрібне значення lvalue. Це висвітлено в розділі 6.5.2.4 Оператори приросту та зменшення Postfix, де сказано ( наголос мій ):

Операнд оператора збільшення або зменшення постфікса повинен мати кваліфікований або некваліфікований дійсний тип або тип вказівника і повинен бути змінним значенням l.

і

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

Книга 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 цікаві, але не так явно , як С. >>правило просять у: stackoverflow.com/questions/15785496 / ...
Чіро Сантіллі郝海东冠状病六四事件法轮功

1
@CiroSantilli 巴拿馬 文件 六四 事件 法轮功 див. Цю відповідь тут
Шафік Ягмур

Приємна подяка, це один із розділів, на який я вказав. Я проголосую вас завтра, коли моя шапка зникне ;-)
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

12

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

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


10
(a++)++ +b

a ++ повертає попереднє значення, значення r. Ви не можете збільшити це.


7

Тому що це спричиняє невизначену поведінку.

Який це?

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

Так, ні ви, ні компілятор цього не знаєте.

РЕДАГУВАТИ:

Справжньою причиною є та, що сказали інші:

Це трактується як (a++)++ + b.

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

Thx до інших, щоб вказати на це.


5
можна сказати те саме для +++ b - (a ++) + b та a + (++ b) мають різні результати.
Michael Chinen

4
насправді постфікс ++ має вищий пріоритет, ніж префікс ++, так a+++bзавждиa++ + b
MByD

4
Я не думаю, що це правильна відповідь, але я можу помилитися. Я думаю, що lexer визначає це як таке, a++ ++ +bяке неможливо проаналізувати.
Лу Франко,

2
Я не погоджуюся з цією відповіддю. „невизначена поведінка” суттєво відрізняється від двозначності токенізації; і я не думаю, що проблема теж.
Jim Blackler

2
"В іншому випадку +++++ b отримає значення ((a ++) ++) + b" ... мій погляд зараз - a+++++b це оцінка до (a++)++)+b. Звичайно, з GCC, якщо ви вставите ці дужки та відновите, повідомлення про помилку не зміниться.
Jim Blackler

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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.