Dlaczego nie działa +++++ b?


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

Ten kod powoduje następujący błąd:

błąd: wartość l wymagana jako operand inkrementacji

Ale jeśli wstawię spacje w całym a++ +i ++b, to działa dobrze.

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

Co oznacza błąd w pierwszym przykładzie?


3
Zaskakujące jest po całym tym czasie, że nikt nie odkrył, że dokładne wyrażenie, o które pytasz, jest używane jako przykład w standardzie C99 i C11. To również dobre wyjaśnienie. Zawarłem to w mojej odpowiedzi.
Shafik Yaghmour

@ShafikYaghmour - To „Przykład 2” w C11 §6.4 Elementy leksykalne ¶6 . Mówi: „Fragment programu x+++++yjest analizowany jako x ++ ++ + y, co narusza ograniczenie operatorów inkrementacji, nawet jeśli analiza x ++ + ++ ymoże dać poprawne wyrażenie”.
Jonathan Leffler

Odpowiedzi:


98

printf("%d",a+++++b);jest interpretowane (a++)++ + bzgodnie z regułą maksymalnego muncha ! .

++(postfiks) nie jest szacowany do an, lvalueale wymaga, aby jego operand był lvalue.

! 6.4 / 4 mówi, że następny token przetwarzania wstępnego jest najdłuższą sekwencją znaków, która może stanowić token przetwarzania wstępnego "


181

Kompilatory są pisane etapami. Pierwszy etap to lekser i zamienia postacie w symboliczną strukturę. Więc „++” staje się czymś w rodzaju enum SYMBOL_PLUSPLUS. Później etap parsera zamienia to w abstrakcyjne drzewo składniowe, ale nie może zmienić symboli. Możesz wpłynąć na leksera, wstawiając spacje (które kończą symbole, chyba że są w cudzysłowach).

Zwykłe leksery są chciwe (z pewnymi wyjątkami), więc twój kod jest interpretowany jako

a++ ++ +b

Dane wejściowe do parsera to strumień symboli, więc Twój kod wyglądałby tak:

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

Która według parsera jest niepoprawna składniowo. (EDYCJA na podstawie komentarzy: semantycznie niepoprawna, ponieważ nie można zastosować ++ do wartości r, co skutkuje a ++)

a+++b 

jest

a++ +b

Co jest w porządku. Podobnie jak inne przykłady.


27
+1 Dobre wyjaśnienie. Muszę jednak poszukać: jest poprawna składniowo, ma po prostu błąd semantyczny (próba zwiększenia wynikającej z lwartości a++).

7
a++daje w wyniku wartość r.
Femaref

9
W kontekście lekserów algorytm „chciwy” jest zwykle nazywany Maximal Munch ( en.wikipedia.org/wiki/Maximal_munch ).
JoeG,

14
Ładny. Wiele języków ma podobne dziwaczne przypadki dzięki zachłannemu leksykaniu. Oto naprawdę dziwny przypadek, w którym wydłużenie wyrażenia sprawia, że ​​jest ono lepsze: w VBScript x = 10&987&&654&&321jest nielegalne, ale, co dziwne, x = 10&987&&654&&&321jest legalne.
Eric Lippert

1
Nie ma to nic wspólnego z chciwością, a wszystko z porządkiem i pierwszeństwem. ++ jest wyższe niż +, więc najpierw zostaną wykonane dwa ++. +++++ b będzie również + ++ ++ b, a nie ++ ++ + b. Podziękowania dla @MByD za łącze.

30

Lekser do tworzenia tokenów używa tak zwanego algorytmu „maksymalnego żucia”. Oznacza to, że czytając znaki, czyta znaki, dopóki nie napotka czegoś, co nie może być częścią tego samego tokena, co już ma (np. Jeśli czyta cyfry, więc to, co ma, jest liczbą, jeśli napotka an A, wie, że nie może być częścią liczby, więc zatrzymuje się i pozostawia Aw buforze wejściowym do użycia jako początek następnego tokenu). Następnie zwraca ten token do parsera.

W tym przypadku oznacza to, że +++++jest leksykowany jako a ++ ++ + b. Ponieważ pierwszy post-inkrementacja daje wartość r, drugiej nie można do niej zastosować, a kompilator zgłasza błąd.

Po prostu FWIW, w C ++ możesz przeciążać, operator++aby uzyskać lwartość, co pozwala na to. Na przykład:

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

Kompiluje się i działa (choć nic nie robi) z kompilatorami C ++, które mam pod ręką (VC ++, g ++, Comeau).


1
"np. jeśli czytał cyfry, więc to, co ma, jest liczbą, jeśli napotka A, wie, że nie może być częścią liczby" 16FAto idealnie dokładna liczba szesnastkowa, która zawiera A.
lublp

1
@nightcracker: tak, ale bez znaku 0xna początku nadal potraktuje to jako 16następującą po nim FAliczbę szesnastkową.
Jerry Coffin

@Jerry Coffin: Nie powiedziałeś, że 0xnie jest częścią numeru.
orlp

@nightcracker: nie, nie zrobiłem tego - biorąc pod uwagę, że większość ludzi nie bierze pod uwagę xcyfry, wydawało się to zupełnie niepotrzebne.
Jerry Coffin

14

Ten dokładny przykład jest omówiony w projekcie normy C99 (te same szczegóły w C11 ), sekcja 6.4 Elementy leksykalne, paragraf 4, który mówi:

Jeśli strumień wejściowy został przeanalizowany na tokeny przetwarzania wstępnego do danego znaku, następny token przetwarzania wstępnego jest najdłuższą sekwencją znaków, która może stanowić token przetwarzania wstępnego. […]

który jest również znany jako reguła maksymalnego chrupania, która jest używana w analizie leksykalnej w celu uniknięcia niejednoznaczności i działa na podstawie jak największej liczby elementów, aby utworzyć prawidłowy token.

akapit zawiera również dwa przykłady, drugi jest dokładnym dopasowaniem do twojego pytania i wygląda następująco:

PRZYKŁAD 2 Fragment programu x +++++ y jest analizowany jako x ++ ++ + y, co narusza ograniczenie operatorów inkrementacji, nawet jeśli analiza x ++ + ++ y może dać prawidłowe wyrażenie.

co mówi nam, że:

a+++++b

zostaną przeanalizowane jako:

a ++ ++ + b

co narusza ograniczenia dotyczące postinkrementacji, ponieważ wynikiem pierwszego postinkrementacji jest rvalue, a po inkrementacji wymaga lwartości. Jest to omówione w sekcji 6.5.2.4 Operatory przyrostowe i dekrementacja, które mówią ( moje podkreślenie ):

Argument operatora zwiększania lub zmniejszania przyrostka powinien mieć kwalifikowaną lub niekwalifikowaną wartość rzeczywistą lub wskaźnikową i być modyfikowalną lwartością.

i

Wynikiem operatora postfix ++ jest wartość operandu.

Książka C ++ Gotchas również omawia ten przypadek w Gotcha #17 Maximal Munch Problems, jest to ten sam problem również w C ++, a także podaje kilka przykładów. Wyjaśnia, że ​​mając do czynienia z następującym zestawem znaków:

->*

analizator leksykalny może zrobić jedną z trzech rzeczy:

  • Traktować go jako trzy tokeny: -, >i*
  • Traktuj to jako dwa żetony: ->i*
  • Traktuj to jako jeden żeton: ->*

Zasada maksymalnego chrupania pozwala uniknąć tych niejednoznaczności. Autor zwraca uwagę, że to ( w kontekście C ++ ):

rozwiązuje o wiele więcej problemów niż powoduje, ale w dwóch typowych sytuacjach jest to uciążliwe.

Pierwszym przykładem byłyby szablony, których argumenty szablonów są również szablonami ( co zostało rozwiązane w C ++ 11 ), na przykład:

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

Który interpretuje zamykające nawiasy kątowe jako operator przesunięcia , więc do ujednoznacznienia wymagana jest spacja:

list< vector<string> > lovos;
                    ^

Drugi przypadek dotyczy domyślnych argumentów wskaźników, na przykład:

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

zostanie zinterpretowany jako *=operator przypisania, rozwiązaniem w tym przypadku jest nazwanie parametrów w deklaracji.


Czy wiesz, która część C ++ 11 określa regułę maksymalnego chrupania? 2.2.3, 2.5.3 są interesujące, ale nie tak wyraźne jak C. >>
Pytanie o regułę

1
@CiroSantilli 巴拿馬 文件 六四 事件 法轮功 zobacz tę odpowiedź tutaj
Shafik Yaghmour

Miłe dzięki, to jedna z sekcji, które wskazałem. Jutro zagłosuję za tobą, kiedy moja czapka się zdejmie ;-)
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

12

Twój kompilator desperacko próbuje przeanalizować a+++++bi interpretuje to jako plik (a++)++ +b. Teraz wynik postinkrementacji ( a++) nie jest lwartością , tj. Nie może być ponownie zwiększony po inkrementacji.

Nigdy nie pisz takiego kodu w programach jakości produkcji. Pomyśl o tym biednym człowieku, który cię ściga i musi zinterpretować twój kod.


10
(a++)++ +b

a ++ zwraca poprzednią wartość, wartość r. Nie możesz tego zwiększyć.


7

Ponieważ powoduje niezdefiniowane zachowanie.

Który to?

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

Tak, ani ty, ani kompilator tego nie wiecie.

EDYTOWAĆ:

Prawdziwym powodem jest ten, o którym mówią inni:

Jest interpretowany jako (a++)++ + b.

ale post-inkrementacja wymaga lwartości (która jest zmienną o nazwie), ale (a ++) zwraca wartość r, której nie można zwiększyć, co prowadzi do otrzymanego komunikatu o błędzie.

Podziękowania dla innych za zwrócenie uwagi.


5
można powiedzieć to samo o a +++ b - (a ++) + b i a + (++ b) mają różne wyniki.
Michael Chinen

4
właściwie, postfix ++ ma wyższy priorytet niż prefiks ++, więc a+++bjest zawszea++ + b
MByD

4
Nie sądzę, żeby to była dobra odpowiedź, ale mogę się mylić. Myślę, że lekser definiuje to jako, a++ ++ +bktórego nie można przeanalizować.
Lou Franco

2
Nie zgadzam się z tą odpowiedzią. „nieokreślone zachowanie” jest czymś zupełnie innym niż niejednoznaczność tokenizacji; i nie sądzę, że jest to problem.
Jim Blackler

2
"W przeciwnym razie +++++ b oceni do ((A ++) ++) + b" ... moim zdaniem w tej chwili jest a+++++b nie ocenia się (a++)++)+b. Z pewnością w GCC, jeśli wstawisz te nawiasy i przebudujesz, komunikat o błędzie się nie zmieni.
Jim Blackler

5

Myślę, że kompilator widzi to jako

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

++musi mieć jako operand wartość, którą można modyfikować. a to wartość, którą można modyfikować. a++jednak jest „wartością r”, nie można jej modyfikować.

Nawiasem mówiąc, błąd, który widzę na GCC C jest taki sam, ale ma inne brzmienie: lvalue required as increment operand .


0

Postępuj zgodnie z tą kolejnością

1. ++ (przed inkrementacją)

2. + - (dodawanie lub odejmowanie)

3. „x” + „y” dodaje obie sekwencje

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

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.