Що значить i = (i, ++ i, 1) + 1; робити?


174

Прочитавши цю відповідь про невизначене поведінку та пункти послідовності, я написав невеличку програму:

#include <stdio.h>

int main(void) {
  int i = 5;
  i = (i, ++i, 1) + 1;
  printf("%d\n", i);
  return 0;
}

Вихід є 2. О Боже, я не бачив, як настав прихід! Що тут відбувається?

Також під час компілювання вищевказаного коду я отримав попередження:

px.c: 5: 8: попередження: лівий операнд вираження комами не впливає

  [-Wunused-value]   i = (i, ++i, 1) + 1;
                        ^

Чому? Але, ймовірно, він автоматично відповість на відповідь мого першого запитання.


289
Не робіть дивних речей, у вас не буде друзів :(
Maroun

9
Попереджувальне повідомлення - це відповідь на ваше перше запитання.
Ю-Хао

2
@gsamaras: ні. отримане значення відкидається, а не модифікація. реальна відповідь: оператор з комами створює точку послідовності.
Каролі Горват

3
@gsamaras Ви не повинні турбуватися, коли у вас є позитивний бал, а ще більше 10 запитань.
LyingOnTheSky

9
Примітка: Оптимізуючий компілятор може зробити простоprintf("2\n");
chux - Поновіть Моніку

Відповіді:


256

У виразі (i, ++i, 1)використовувана кома є оператором коми

оператор комами (представлений маркером ,) - це двійковий оператор, який оцінює свій перший операнд і відкидає результат, а потім оцінює другий операнд і повертає це значення (і тип).

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

Отже, у вищенаведеному виразі iбуде оцінено крайнє ліве місце , а його значення буде відкинуто. Тоді ++iбуде оцінено і збільшиться iна 1 і знову значення виразу ++iбуде відкинуто, але побічний ефект до iпостійного . Тоді 1буде оцінено і значення виразу буде 1.

Це еквівалентно

i;          // Evaluate i and discard its value. This has no effect.
++i;        // Evaluate i and increment it by 1 and discard the value of expression ++i
i = 1 + 1;  

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


1
хоча остаточне вираження є дійсним, чи не другий вираз ++ i невизначена поведінка? вона оцінюється і значення неініціалізованої змінної попередньо збільшується, що не так? чи я щось пропускаю?
Кушик Шетті

2
@Koushik; iініціалізовано з 5. Подивіться на заяву декларації int i = 5;.
hackcks

1
ой мій поганий. Вибачте, я чесно бачу це бачити.
Кушик Шетті

Тут є помилка: ++ я збільшить, а потім оцінюю її, тоді як i ++ оцінить, а потім збільшить її.
Квентін Гайот

1
@QuentinHayot; Що? Будь-які побічні ефекти мають місце після оцінки вираженості. У випадку ++iцього вираження буде оцінено, iбуде збільшено, і це збільшене значення буде значенням виразу. У випадку i++цього вираження буде оцінено, старе значення iбуде значенням виразу, iзбільшується в будь-який час між попередньою та наступною точкою послідовності виразу.
хаки

62

Цитуючи з C11, глава 6.5.17, оператор Comma

Лівий операнд оператора комами оцінюється як недійсний вираз; існує точка послідовності між її оцінкою та правою операндом. Потім оцінюється правий операнд; результат має свій тип та значення.

Отже, у вашому випадку

(i, ++i, 1)

оцінюється як

  1. i, оцінюється як недійсний вираз, значення, яке відкидається
  2. ++i, оцінюється як недійсний вираз, значення, яке відкидається
  3. нарешті, 1значення повернулося.

Отже, виглядає остаточне твердження

i = 1 + 1;

і iдістається 2. Я думаю, це відповідає на обидва ваші запитання,

  • Як iотримується значення 2?
  • Чому виникає попереджувальне повідомлення?

Примітка: FWIW, оскільки після оцінки лівого операнда присутній пункт послідовності , такий вираз, як (i, ++i, 1)не викликає UB, як можна вважати, помилково.


+1 Сурав, оскільки це пояснює, чому інтіалізація iявно не має ефекту! Однак я не думаю, що це було настільки очевидним для хлопця, який не знає оператора комами (і я не знав, як шукати допомогу, крім запитання). Шкода, що я отримав стільки знижок! Я перевірю інші відповіді, а потім вирішу, яку прийняти. Дякую! Приємна відповідь btw.
gsamaras

Я відчуваю, що мушу пояснити, чому я прийняв відповідь на хаки. Я був готовий прийняти ваше, оскільки це справді відповідає на мої обидва питання. Однак якщо ви перевірите коментарі до мого питання, ви побачите, що деякі люди не можуть побачити з першого погляду, чому це не викликає UB. відповіді на хаки містять релевантну інформацію. Звичайно, я маю відповідь щодо UB, пов'язану в моєму питанні, але деякі люди можуть це пропустити. Сподіваюся, ви згодні з моїм рішенням, якщо не дасте мені знати. :)
gsamaras

30
i = (i, ++i, 1) + 1;

Давайте проаналізуємо це поетапно.

(i,   // is evaluated but ignored, there are other expressions after comma
++i,  // i is updated but the resulting value is ignored too
1)    // this value is finally used
+ 1   // 1 is added to the previous value 1

Таким чином ми отримуємо 2. І остаточне завдання зараз:

i = 2;

Що б не було в i до того, як зараз його перезаписати.


Було б непогано констатувати, що це відбувається через оператор коми. Хоча +1 за покроковий аналіз! Приємна відповідь btw.
gsamaras

Вибачте за недостатнє пояснення, у мене є лише записка ( ... але проігноровано, є ... ). Я хотів пояснити, головним чином, чому ++iце не сприяє результату.
тьмяний

тепер мій для циклів завжди буде якint i = 0; for( ;(++i, i<max); )
CoffeDeveloper

19

Підсумок

(i, ++i, 1)

є

1

Для

(i,++i,1) 

оцінка буває такою, що ,оператор відкидає оцінене значення і збереже саме найвище значення, яке є1

Так

i = 1 + 1 = 2

1
Так, я теж про це думав, але не знаю чому!
gsamaras

@gsamaras тому, що оператор кома оцінює попередній термін, але відкидає його (тобто не використовує його для виконання завдань тощо)
Марко А.

14

Ви знайдете хороше прочитання на сторінці вікі для оператора Comma .

В основному, це

... оцінює свій перший операнд і відкидає результат, а потім оцінює другий операнд і повертає це значення (і тип).

Це означає що

(i, i++, 1)

буде, у свою чергу, оцінювати i, відкидати результат, оцінювати i++, відкидати результат, а потім оцінювати та повертати 1.


O_O пекло, чи синтаксис дійсний на C ++, я пам’ятаю, що в мене було небагато місць, де мені знадобився цей синтаксис (в основному я писав: (void)exp; a= exp2;поки мені просто потрібно a = exp, exp2;)
CoffeDeveloper

13

Вам потрібно знати, що тут робить оператор комами:

Ваше вираження:

(i, ++i, 1)

Перший вираз i,, оцінюється, другий вираз, ++iоцінюється, а третій вираз, 1повертається для всього виразу.

Таким чином , результат: i = 1 + 1.

На ваше бонусне питання, як бачите, перший вираз iвзагалі не має ефекту, тому компілятор скаржиться.


5

Кома має "зворотний" пріоритет. Це ви отримаєте зі старих книг та посібників з C від IBM (70-ті / 80-ті роки). Отже, остання "команда" - це те, що використовується в батьківському виразі.

У сучасному C його використання дивне, але дуже цікаве для старих C (ANSI):

do { 
    /* bla bla bla, consider conditional flow with several continue's */
} while ( prepAnything(), doSomethingElse(), logic_operation);

Хоча всі операції (функції) викликаються зліва направо, в результаті буде використано лише останнє вираження до умовного 'while'. Це запобігає обробці "goto" для збереження унікального блоку команд для запуску перед перевіркою умови.

EDIT: Це дозволяє також уникнути виклику функції обробки, яка могла б піклуватися про всю логіку в лівих операндах і таким чином повертати логічний результат. Пам'ятайте, що ми не виконували вбудованої функції в минулому C. Отже, це може уникнути накладних викликів.


Лучано, ви також отримали посилання на цю відповідь: stackoverflow.com/questions/17902992/… .
gsamaras

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