Dlaczego c = ++ (a + b) daje błąd kompilacji?

111

Po zbadaniu przeczytałem, że operator inkrementacji wymaga, aby operand miał modyfikowalny obiekt danych: https://en.wikipedia.org/wiki/Increment_and_decrement_operators .

Z tego sądzę, że daje błąd kompilacji, ponieważ (a+b)jest to tymczasowa liczba całkowita, więc nie można jej modyfikować.

Czy to rozumienie jest prawidłowe? To był mój pierwszy raz, kiedy próbowałem zbadać problem, więc jeśli było coś, czego powinienem był szukać, proszę o poradę.

dng
źródło
35
To nie jest złe z punktu widzenia badań. Jesteś na dobrej drodze.
StoryTeller - Unslander Monica
35
Czego oczekujesz od wyrażenia?
qrdl
4
zgodnie ze standardem C11 6.5.3.1: Operand operatora zwiększania lub zmniejszania przedrostka powinien mieć atomową, kwalifikowaną lub niekwalifikowaną liczbę rzeczywistą lub wskaźnikową oraz modyfikowalną lwartość
Christian Gibbons
10
Jak chciałbyś, aby 1 został rozdzielony między a i b? „Czy indeksy tablicowe powinny zaczynać się od 0 czy 1? Mój kompromis 0,5 został odrzucony bez, jak sądziłem, odpowiedniego rozważenia”. - Stan Kelly-Bootle
Andrew Morton
5
Myślę, że następujące pytanie brzmi: dlaczego miałbyś kiedykolwiek chcieć to zrobić, skoro c = a + b + 1twój zamiar jest jaśniejszy, a także krótszy do wpisania. Operatory inkrementacji / dekrementacji robią dwie rzeczy: 1. one i ich argument tworzą wyrażenie (którego można użyć np. W pętli for), 2. modyfikują argument. W swoim przykładzie używasz właściwości 1, ale nie właściwości 2, ponieważ odrzucasz zmodyfikowany argument. Jeśli nie potrzebujesz właściwości 2. i potrzebujesz tylko wyrażenia, możesz po prostu napisać wyrażenie, np. X + 1 zamiast x ++.
Trevor

Odpowiedzi:

117

To tylko reguła, to wszystko i prawdopodobnie istnieje po to, aby (1) ułatwić pisanie kompilatorów C i (2) nikt nie przekonał komitetu normalizacyjnego C do złagodzenia tego problemu.

Mówiąc nieformalnie, możesz pisać tylko ++foowtedy, gdy foomoże pojawić się po lewej stronie wyrażenia przypisania, takiego jak foo = bar. Ponieważ nie możesz pisać a + b = bar, nie możesz też pisać ++(a + b).

Nie ma prawdziwego powodu, dla a + bktórego nie można by uzyskać tymczasowego, na którym ++można by operować, a wynikiem tego jest wartość wyrażenia ++(a + b).

Batszeba
źródło
4
Myślę, że punkt (1) uderza w gwóźdź w głowę. Samo przyjrzenie się regułom tymczasowej materializacji w C ++ może przyprawić o zawrót głowy (ale jest to potężne, trzeba to powiedzieć).
StoryTeller - Unslander Monica
4
@StoryTeller: Rzeczywiście, w przeciwieństwie do naszego ukochanego języka C ++, C nadal stosunkowo trywialnie kompiluje się do asemblera.
Batszeba
29
Oto prawdziwy powód IMHO: byłoby to straszne zamieszanie, gdyby ++czasami miał efekt uboczny modyfikacji czegoś, a czasami po prostu nie.
aschepler
5
@dng: Rzeczywiście jest; dlatego wprowadzono terminy lwartość i rwartość, chociaż obecnie sprawy są bardziej skomplikowane (szczególnie w C ++). Na przykład stała nigdy nie może być lwartością: coś takiego jak 5 = a nie ma sensu.
Batszeba
6
@Bathsheba To wyjaśnia, dlaczego 5 ++ powoduje również błąd kompilacji
dng
40

Standard C11 stwierdza w sekcji 6.5.3.1

Operand operatora zwiększania lub zmniejszania przedrostka powinien mieć niepodzielną, kwalifikowaną lub niekwalifikowaną wartość rzeczywistą lub wskaźnikową i być modyfikowalną lwartością

A „modyfikowalna lwartość” jest opisana w sekcji 6.3.2.1, podrozdziale 1

Wartość l jest wyrażeniem (z typem obiektu innym niż void), które potencjalnie określa obiekt; jeśli lwartość nie oznacza obiektu podczas oceny, zachowanie jest nieokreślone. Kiedy mówi się, że obiekt ma określony typ, typ jest określany przez lwartość używaną do oznaczenia obiektu. Modyfikowalna lwartość to wartość l, która nie ma typu tablicy, nie ma niekompletnego typu, nie ma typu kwalifikowanego jako stała, a jeśli jest to struktura lub unia, nie ma żadnego elementu członkowskiego (w tym rekurencyjnie żadnego elementu członkowskiego lub element wszystkich zawartych agregacji lub unii) z typem z klauzulą ​​const.

Więc (a+b)nie jest modyfikowalną lwartością i dlatego nie kwalifikuje się do operatora zwiększania przedrostka.

Christian Gibbons
źródło
1
Brakuje twojego wniosku z tych definicji ... Chcesz powiedzieć, że (a + b) potencjalnie nie oznacza obiektu, ale te akapity na to nie pozwalają.
hkBst
21

Masz rację. ++próbuje przypisać nową wartość do zmiennej pierwotnej. Więc ++aweźmie wartość a, doda 1do niej, a następnie przypisze z powrotem do a. Ponieważ, jak powiedziałeś, (a + b) jest wartością tymczasową, a nie zmienną z przypisanym adresem pamięci, przypisanie nie może zostać wykonane.

Roee Gavirel
źródło
12

Myślę, że głównie odpowiedziałeś na własne pytanie. Mogę dokonać niewielkiej zmiany w Twoim frazie i zastąpić „zmienną tymczasową” „wartością r”, jak wspomniał C.Gibbons.

Terminy zmienna, argument, zmienna tymczasowa i tak dalej staną się bardziej zrozumiałe, gdy dowiesz się o modelu pamięci C (wygląda to na ładne omówienie: https://www.geeksforgeeks.org/memory-layout-of-c-program/ ).

Termin „wartość r” może wydawać się nieprzejrzysty, kiedy dopiero zaczynasz, więc mam nadzieję, że poniższe wskazówki pomogą rozwinąć intuicję na jego temat.

Lwartość / rwartość mówią o różnych stronach znaku równości (operator przypisania): lwartość = lewa strona (mała litera L, a nie „jedynka”) rwartość = prawa strona

Dowiedz się trochę o tym, jak C używa pamięci (i rejestrów), aby zrozumieć, dlaczego rozróżnienie jest ważne. W szerokich pociągnięciach pędzla kompilator tworzy listę instrukcji języka maszynowego, które obliczają wynik wyrażenia (wartość r), a następnie umieszcza ten wynik gdzieś (lwartość). Wyobraź sobie kompilator obsługujący następujący fragment kodu:

x = y * 3

W pseudokodzie asemblera może to wyglądać podobnie do tego przykładu zabawki:

load register A with the value at memory address y
load register B with a value of 3
multiply register A and B, saving the result in A
write register A to memory address x

Operator ++ (i jego odpowiednik) potrzebują „gdzieś” do zmodyfikowania, w zasadzie wszystkiego, co może działać jako lwartość.

Zrozumienie modelu pamięci C będzie pomocne, ponieważ uzyskasz lepsze wyobrażenie o tym, jak argumenty są przekazywane do funkcji i (ostatecznie) jak pracować z dynamiczną alokacją pamięci, taką jak funkcja malloc (). Z podobnych powodów możesz w pewnym momencie przestudiować proste programowanie w asemblerze, aby uzyskać lepsze pojęcie o tym, co robi kompilator. Również jeśli używasz gcc , opcja -S "Zatrzymaj po etapie właściwej kompilacji; nie asembluj". może być interesujący (choć polecam wypróbowanie go na małym fragmencie kodu).

Na marginesie: instrukcja ++ istnieje od 1969 roku (chociaż zaczęła się w poprzedniku C, B):

(Kena Thompsona) obserwacja (była), że tłumaczenie ++ x było mniejsze niż tłumaczenie x = x + 1. "

Po tym odnośniku wikipedii przejdziesz do interesującego artykułu Dennisa Ritchiego („R” w „K&R C”) na temat historii języka C, z linkiem tutaj dla wygody: http://www.bell-labs.com/ usr / dmr / www / chist.html, gdzie możesz wyszukać „++”.

jgreve
źródło
6

Powodem jest to, że standard wymaga, aby operand był lwartością. Wyrażenie (a+b)nie jest lwartością, więc stosowanie operatora przyrostu jest niedozwolone.

Teraz można by powiedzieć: „OK, to rzeczywiście powód, ale tak naprawdę nie ma innego * prawdziwego * powodu niż ten” , ale niestety konkretne sformułowanie tego, jak operator faktycznie działa, wymaga, aby tak było.

Wyrażenie ++ E jest równoważne z (E + = 1).

Oczywiście nie możesz pisać, E += 1jeśli Enie jest lwartością. Szkoda, bo równie dobrze można było powiedzieć: „zwiększa E o jeden” i gotowe. W takim przypadku zastosowanie operatora na wartości innej niż l byłoby (w zasadzie) całkowicie możliwe, kosztem nieco bardziej złożonego kompilatora.

Definicja mogłaby zostać w trywialny sposób przeformułowana (myślę, że nie jest to nawet pierwotnie C, ale dziedzictwo B), ale zrobienie tego zasadniczo zmieniłoby język na coś, co nie jest już zgodne z jego poprzednimi wersjami. Ponieważ możliwa korzyść jest raczej niewielka, ale możliwe konsekwencje są ogromne, to się nigdy nie wydarzyło i prawdopodobnie nigdy się nie wydarzy.

Jeśli weźmiesz pod uwagę C ++ oprócz C (pytanie jest oznaczone tagiem C, ale była dyskusja na temat przeciążenia operatorów), historia staje się jeszcze bardziej skomplikowana. W C trudno sobie wyobrazić, że tak może być, ale w C ++ rezultatem (a+b)może być coś, czego w ogóle nie można zwiększyć, lub inkrementacja może mieć bardzo znaczące skutki uboczne (nie tylko dodanie 1). Kompilator musi być w stanie sobie z tym poradzić i zdiagnozować problematyczne przypadki, gdy się pojawią. W przypadku lwartości sprawdzenie tego jest trochę trywialne. Inaczej jest w przypadku jakiegokolwiek przypadkowego wyrażenia w nawiasach, które rzucasz na biedaka.
To nie jest prawdziwy powód, dla którego nie mógł być zrobione, ale z pewnością jest to wyjaśnienie, dlaczego ludzie, którzy to wdrożyli, nie są do końca zachwyceni, dodając taką cechę, która obiecuje bardzo niewielkie korzyści bardzo niewielu ludziom.

Damon
źródło
3

(a + b) zwraca wartość r, której nie można zwiększać.

Casper B. Hansen
źródło
3

++ próbuje nadać wartość oryginalnej zmiennej, a ponieważ (a + b) jest wartością tymczasową, nie może wykonać operacji. I są to w zasadzie zasady konwencji programowania C, które ułatwiają programowanie. Otóż ​​to.

Babu Chandermani
źródło
2

Gdy wykonywane jest wyrażenie ++ (a + b), to na przykład:

int a, b;
a = 10;
b = 20;
/* NOTE :
 //step 1: expression need to solve first to perform ++ operation over operand
   ++ ( exp );
// in your case 
   ++ ( 10 + 20 );
// step 2: result of that inc by one 
   ++ ( 30 );
// here, you're applying ++ operator over constant value and it's invalid use of ++ operator 
*/
++(a+b);
Jeet Parikh
źródło