Что такое я = (i, ++ i, 1) + 1; делать?
Прочитав этот ответ о поведении и точках последовательности undefined, я написал небольшую программу:
#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;
^
Почему? Но, вероятно, ответ будет автоматически отвечать на мой первый вопрос.
Ответы
Ответ 1
В выражении (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;
Обратите внимание, что приведенное выше выражение отлично действует и не вызывает поведение undefined, потому что существует точка последовательности между оценкой левый и правый операнды оператора запятой.
Ответ 2
Цитирование из C11
, главы 6.5.17
, Comma operator
Левый операнд оператора запятой оценивается как выражение void; Eсть точка последовательности между ее оценкой и точкой правильного операнда. Тогда правая операнд оценивается; результат имеет свой тип и значение.
Итак, в вашем случае
(i, ++i, 1)
оценивается как
-
i
, оценивается как выражение void, значение отбрасывается
-
++i
, оценивается как выражение void, значение отбрасывается
- наконец,
1
, возвращаемое значение.
Итак, заключительный оператор выглядит как
i = 1 + 1;
и i
получает значение 2
. Думаю, это отвечает на оба ваших вопроса,
- Как
i
получает значение 2?
- Почему появляется предупреждающее сообщение?
Примечание: FWIW, поскольку после оценки левого операнда присутствует точка последовательности, выражение, подобное (i, ++i, 1)
, не будет вызывать UB, поскольку, как правило, можно подумать по ошибке.
Ответ 3
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, прежде чем он будет перезаписан.
Ответ 4
Результат
(i, ++i, 1)
является
1
Для
(i,++i,1)
оценка происходит так, что оператор ,
отбрасывает оцениваемое значение и сохраняет только самое правое значение, которое 1
So
i = 1 + 1 = 2
Ответ 5
Вы найдете хорошее чтение на странице wiki для оператора Comma.
В принципе, он
... оценивает свой первый операнд и отбрасывает результат, а затем оценивает второй операнд и возвращает это значение (и тип).
Это означает, что
(i, i++, 1)
будет, в свою очередь, оценивать i
, отбрасывать результат, оценивать i++
, отбрасывать результат, а затем оценивать и возвращать 1
.
Ответ 6
Вам нужно знать, что делает здесь оператор запятой:
Ваше выражение:
(i, ++i, 1)
Вычисляется первое выражение i
, второе выражение ++i
оценивается, а третье выражение 1
возвращается для всего выражения.
Итак, результат: i = 1 + 1
.
Для вашего бонусного вопроса, как вы видите, первое выражение i
не имеет никакого эффекта, поэтому компилятор жалуется.
Ответ 7
Запятая имеет "обратное" приоритет. Это то, что вы получите от старых книг и руководств C от IBM (70s/80s). Поэтому последняя "команда" - это то, что используется в родительском выражении.
В современном C его использование странно, но очень интересно в старом C (ANSI):
do {
/* bla bla bla, consider conditional flow with several continue */
} while ( prepAnything(), doSomethingElse(), logic_operation);
В то время как все операции (функции) вызываются слева направо, в качестве условного "while" будет использоваться только последнее выражение.
Это предотвращает обработку "goto", чтобы сохранить уникальный блок команд для запуска до проверки условий.
EDIT: Это позволяет избежать вызова функции обработки, которая может позаботиться обо всей логике в левых операндах и вернуть логический результат. Помните, что в прошлом у нас не было встроенной функции C. Таким образом, это могло бы избежать накладных расходов на вызов.