Что значит 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;
                        ^

Зачем? Но, вероятно, на него автоматически ответит ответ на мой первый вопрос.

gsamaras
источник
289
Не делай странных вещей, у тебя не будет друзей :(
Maroun
9
Предупреждающее сообщение является ответом на ваш первый вопрос.
Ю Хао
2
@gsamaras: нет. результирующее значение отбрасывается, а не модификация. реальный ответ: оператор запятой создает точку последовательности.
Кароли Хорват
3
@gsamaras Вы не должны заботиться, когда у вас есть положительный счет и даже больше с 10+ вопрос.
LyingOnTheSky
9
Примечание: оптимизирующий компилятор может просто сделатьprintf("2\n");
chux - восстановить Monica

Ответы:

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;  

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

haccks
источник
1
хотя последнее выражение допустимо, не является ли второе выражение ++ i неопределенным поведением? оно оценивается, и значение неинициализированной переменной предварительно увеличивается, что не так? или я что-то упустил?
Кушик Шетти
2
@Koushik; iинициализируется с 5. Посмотрите на заявление декларации int i = 5;.
хак
1
о мой плохой Извините, я, честно говоря, не вижу этого.
Кушик Шетти
Здесь есть ошибка: ++ я буду увеличивать, а затем оценивать, в то время как i ++ будет оценивать, а затем увеличивать.
Квентин Хайот
1
@QuentinHayot; Какой? Любые побочные эффекты имеют место после оценки выражения. В случае ++i, это выражение будет оценено, iбудет увеличено, и это увеличенное значение будет значением выражения. В случае i++, если это выражение будет оценено, старое значение iбудет значением выражения, iбудет увеличено в любой момент между предыдущей и следующей точкой последовательности выражения.
хак
62

Цитирование из C11главы 6.5.17, оператор запятой

Левый операнд оператора запятой оценивается как пустое выражение; существует последовательность между его оценкой и оценкой правого операнда. Затем вычисляется правый операнд; результат имеет свой тип и значение.

Итак, в вашем случае,

(i, ++i, 1)

оценивается как

  1. i, оценивается как пустое выражение, значение отбрасывается
  2. ++i, оценивается как пустое выражение, значение отбрасывается
  3. наконец, 1возвращаемое значение.

Итак, окончательное утверждение выглядит так

i = 1 + 1;

и iдобирается до 2. Я думаю, это отвечает на оба ваших вопроса,

  • Как iполучить значение 2?
  • Почему появляется предупреждающее сообщение?

Примечание: FWIW, так как есть точка последовательность присутствует после оценки левой руки операнда, выражение типа (i, ++i, 1)не Invoke UB, так как один может вообще думать по ошибке.

Сурав Гош
источник
+1 Сурав, поскольку это объясняет, почему инициализация iявно не имеет никакого эффекта! Тем не менее, я не думаю, что это было так очевидно для парня, который не знает оператора запятой (и я не знал, как искать помощь, кроме как задать вопрос). Жаль, я получил так много отрицательных голосов! Я проверю другие ответы и затем решу, что принять. Спасибо! Хороший топ-ответ, кстати.
gsamaras
Я чувствую, что должен объяснить, почему я принял ответ хаков. Я был готов принять ваш, так как он действительно отвечает на оба моих вопроса. Однако, если вы проверите комментарии на мой вопрос, вы увидите, что некоторые люди на первый взгляд не могут понять, почему это не вызывает UB. Ответы haccks предоставляют некоторую соответствующую информацию. Конечно, у меня есть ответ относительно 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;

Что бы ни было во мне раньше, оно сейчас перезаписывается.

dlask
источник
Было бы неплохо заявить, что это происходит из-за оператора запятой. +1 для пошагового анализа, хотя! Хороший топ-ответ, кстати.
gsamaras
Я прошу прощения за недостаточное объяснение, у меня есть только записка ( ... но игнорируются, есть ... ). Я хотел объяснить главным образом, почему ++iэто не способствует результату.
Сумерки
теперь мои циклы for всегда будут похожи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

На вики-странице вы найдете полезные материалы для оператора запятой .

В основном это

... вычисляет свой первый операнд и отбрасывает результат, а затем оценивает второй операнд и возвращает это значение (и тип).

Это означает, что

(i, i++, 1)

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

Томас Ашан
источник
О_О, черт возьми, этот синтаксис действителен в C ++, я помню, у меня было несколько мест, где мне был нужен этот синтаксис (в основном я писал: (void)exp; a= exp2;пока мне просто нужно a = exp, exp2;)
CoffeDeveloper
13

Вы должны знать, что делает оператор запятой здесь:

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

(i, ++i, 1)

Первое выражение, iоценивается, второе выражение, ++iоценивается, и третье выражение,1 возвращается для всего выражения.

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

На ваш бонусный вопрос, как вы видите, первое выражение не iимеет никакого эффекта, поэтому компилятор жалуется.

songyuanyao
источник
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's', чтобы сохранить уникальный блок команд для запуска перед проверкой состояния.

РЕДАКТИРОВАТЬ: Это также позволяет избежать вызова функции обработки, которая может позаботиться о всей логике в левых операндах и таким образом вернуть логический результат. Помните, что у нас не было встроенной функции в прошлом C. Таким образом, это могло избежать издержек вызова.

Лучано
источник
Лучано, у тебя также есть ссылка на этот ответ: stackoverflow.com/questions/17902992/… .
Гсамарас
В начале 90-х годов, прежде чем использовать встроенные функции, я много использовал их для оптимизации и поддержания организованности кода.
Лучано,