Чому c = ++ (a + b) дає помилку компіляції?


111

Після дослідження я прочитав, що оператор приросту вимагає від операнда об'єкт даних, що змінюється: https://en.wikipedia.org/wiki/Increment_and_decrement_operators .

Звідси я здогадуюсь, що це дає помилку компіляції, оскільки (a+b)це тимчасове ціле число і тому не піддається зміні.

Чи правильно це розуміння? Це був мій перший раз, коли я намагався дослідити проблему, тож якщо щось було, я б шукав, будь ласка, поради.


35
Це не погано з точки зору досліджень. Ви на правильному шляху.
StoryTeller - Невідповідна Моніка

35
Що ви очікуєте від цього виразу?
qrdl

4
відповідно до стандарту C11 6.5.3.1: Операнд оператора збільшення або зменшення префікса повинен мати атомний, кваліфікований або некваліфікований реальний або вказівний тип і повинен бути модифікованим значенням
Крістіан Гіббонс

10
Як би ви хотіли, щоб 1 розподілявся між a і b? "Чи повинні індекси масиву починатися з 0 або 1? Мої компроміси 0,5 були відхилені без, я вважав, належного розгляду." - Стен Келлі-Бутл
Ендрю Мортон

5
Я думаю, що наступне питання - чому ви коли-небудь хочете це робити, коли c = a + b + 1ваш намір ясніший, а також скорочується. Оператори збільшення / зменшення виконують дві речі 1. вони та їх аргумент утворюють вираз (який можна використовувати, наприклад, у циклі for), 2. вони змінюють аргумент. У вашому прикладі ви використовуєте властивість 1., але не властивість 2., оскільки ви викидаєте змінений аргумент. Якщо вам не потрібна властивість 2. і ви просто хочете вираз, тоді ви можете просто написати вираз, наприклад, x + 1 замість x ++.
Тревор

Відповіді:


117

Це просто правило, це все, і, можливо, існує можливість (1) спростити писати компілятори C і (2) ніхто не переконав комітет зі стандартів С розслабити його.

Неформально кажучи, ви можете писати, лише ++fooякщо вони fooможуть з’являтися зліва від виразу призначення, як-от foo = bar. Оскільки ви не вмієте писати a + b = bar, ви також не можете писати ++(a + b).

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


4
Я думаю, що точка (1) потрапляє на ніготь по голові. Якщо подивитися на правила тимчасової матеріалізації в C ++, ви можете перетворити свій шлунок (але це все-таки потужно, треба сказати про це).
StoryTeller - Невідповідна Моніка

4
@StoryTeller: Дійсно, на відміну від нашої улюбленої мови C ++, C все ще збирається в збірку порівняно тривіально.
Вірсавія

29
Ось справжня причина ІМХО: це було б жахливою плутаниною, якби ++іноді виникав побічний ефект від того, щоб щось змінити, а іноді просто не.
aschepler

5
@dng: Дійсно так і є; ось чому були введені терміни lvalue та rvalue, хоча все є складнішим, ніж сьогодні (особливо в C ++). Наприклад, константа ніколи не може бути цінністю: щось на зразок 5 = a не має сенсу.
Вірсавія

6
@Bathsheba Це пояснює, чому 5 ++ також викликає помилку компіляції
dng

40

Стандарт C11 зазначено в розділі 6.5.3.1

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

І "змінене значення" описано в підрозділі 1 розділу 6.3.2.1

Lvalue - це вираз (з типом об'єкта, відмінним від пустоти), який потенційно позначає об'єкт; якщо значення lvalue не позначає об'єкт при його оцінці, поведінка не визначена. Коли кажуть, що об'єкт має певний тип, тип визначається рівнем значення, яке використовується для позначення об'єкта. Змінюване значення lvalue - це значення, яке не має типу масиву, не має неповного типу, не має типу const, і якщо це структура або об'єднання, не має жодного члена (включаючи, рекурсивно, будь-який член або елемент усіх агрегатів або об'єднань, що містяться) з типом, що відповідає кваліфікації.

Таким чином (a+b), не є модифікованим рівнем і тому не підходить для оператора збільшення префікса.


1
Ваш висновок з цих визначень відсутній ... Ви хочете сказати, що (a + b) потенційно не позначає об'єкт, але ці пункти цього не дозволяють.
hkBst

21

Ви праві. ++намагається привласнити нове значення вихідної змінної. Таким чином ++a, візьмемо значення a, додає 1до нього і потім присвоїть його назад a. Оскільки, як ви сказали, (a + b) є тимчасовим значенням, а не змінною з призначеною адресою пам'яті, призначення не може бути виконано.


12

Думаю, ви здебільшого відповіли на власне запитання. Я можу внести невелику зміну до вашого фразування та замінити "тимчасову змінну" на "rvalue", як згадував C.Gibbons.

Змінна терміни, аргумент, тимчасова змінна та інше стануть більш зрозумілими, коли ви дізнаєтесь про модель пам'яті C (це виглядає як приємний огляд: https://www.geeksforgeeks.org/memory-layout-of-c-program/ ).

Термін "rvalue" може здатися непрозорим, коли ви тільки починаєте, тому я сподіваюся, що наступне допомагає розвинути інтуїцію щодо цього.

Lvalue / rvalue говорять про різні сторони знаку рівності (оператор присвоєння): lvalue = ліва сторона (нижня літера L, а не "одна") rvalue = права сторона

Дізнатися трохи про те, як C використовує пам'ять (і регістри), буде корисним, щоб зрозуміти, чому розрізнення важливе. У широких штрихах пензлем компілятор створює список машинних мовних інструкцій, які обчислюють результат вираження (rvalue) і потім ставлять цей результат десь (lvalue). Уявіть компілятор, що займається наступним фрагментом коду:

x = y * 3

У псевдокоді збірки це може виглядати приблизно як такий приклад іграшки:

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

Оператору ++ (і його - аналогу) потрібно "десь" змінити, по суті, все, що може працювати як значення.

Розуміння моделі пам'яті C буде корисним, оскільки ви отримаєте краще уявлення в голові про те, як аргументи передаються функціям та (зрештою), як працювати з динамічним розподілом пам'яті, як функція malloc (). З подібних причин ви можете вивчити просте програмування складання в якийсь момент, щоб краще зрозуміти, що робить компілятор. Також якщо ви використовуєте gcc , опція -S "Зупинитись після належного етапу компіляції; не збирайте". може бути цікавим (хоча рекомендую спробувати його на невеликому фрагменті коду).

Як і вбік: інструкція ++ існує з 1969 року (хоча вона почалася ще у попередника C, B):

(Спостереження Кена Томпсона) було, що переклад ++ x був меншим, ніж переклад x = x + 1. "

Після цього посилання на вікіпедію перенесе вас до цікавого опису Денніса Річі ("R" в "K&R C") про історію мови C, зв'язаний тут для зручності: http://www.bell-labs.com/ usr / dmr / www / chist.html, де можна шукати "++".


6

Причина полягає в тому, що стандарт вимагає, щоб операнд був значенням. Вираз (a+b)не має значення, тому застосування оператора приросту заборонено.

Тепер, можна сказати: «Добре, це справді причина, але насправді немає * реальної * причини, крім цього» , але, на жаль, конкретна формуляція того, як оператор фактично працює, вимагає, щоб це було так.

Вираз ++ E еквівалентно (E + = 1).

Очевидно, ви не можете написати, E += 1якщо Eце не значення. Що шкода, тому що можна було б так само сказати: "Зростання Е по одному" і робити. У такому випадку застосування оператора на неістотному значенні було б (в принципі) цілком можливим за рахунок ускладнення компілятора.

Тепер це визначення може бути тривіально перероблене (я думаю, що це навіть не спочатку C, але є спадкоємство B), але це кардинально змінить мову на щось, що вже не сумісне з її колишніми версіями. Оскільки можлива користь досить мала, але можливі наслідки величезні, цього ніколи не було і, мабуть, ніколи не відбудеться.

Якщо розглядати C ++ на додаток до C (питання позначено тегом C, але обговорювалося перевантаження операторів), історія стає ще складнішою. У С важко уявити, що це може бути так, але в С ++ результатом (a+b)може бути щось таке, чого ти взагалі не можеш збільшити, або збільшення може мати дуже значні побічні ефекти (не просто додавання 1). Компілятор повинен бути в змозі впоратися з цим і діагностувати проблемні випадки в процесі їх виникнення. Щодо lvalue, це все-таки тривіально перевірити. Не так для будь-якого випадкового вираження всередині дужок, яке ви кидаєте на бідне.
Це не реальна причина, чому вона не могла це може бути зроблено, але це, безумовно, дає пояснення, чому люди, які це втілили, не є абсолютно захопленими, щоб додати таку особливість, яка обіцяє дуже мало користі для небагатьох людей.



3

++ намагається надати значення вихідній змінній, і оскільки (a + b) є тимчасовим значенням, воно не може виконати операцію. Вони в основному є правилами конвенцій програмування С, щоб спростити програмування. Це воно.


2

Коли виконується вираз ++ (a + b), то, наприклад:

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