Ответ 1
Поскольку этот вопрос задавался из-за обсуждения на основе комментариев here, я предоставлю некоторый контекст:
первый комментарий: порядок операций не гарантирован быть порядком, в котором вы передаете аргументы функции. Некоторые люди (ошибочно) предполагают, что аргументы будут оцениваться справа налево, но в соответствии со стандартом поведение undefined.
OP принимает и понимает это. Нет смысла повторять тот факт, что your_function(++i, ++i)
является UB.
В ответ на этот комментарий: благодаря вашему комментарию я вижу, что
printf
может быть оценен в любом порядке, но я понял, что это потому, что аргументыprintf
являются частьюva_list
. Вы говорите, что аргументы любой функции выполняются в произвольном порядке?
OP просит уточнить, поэтому я немного уточнил:
Второй комментарий: Да, это именно то, что я говорю. даже вызов
int your_function(int a, int b) { return a - b; }
не гарантирует, что передаваемые вами выражения будут оцениваться слева направо. Нет точки последовательности (точка, в которой выполняются все побочные эффекты предыдущих оценок). Возьмите этот пример. Вложенный вызов является точкой последовательности, поэтому внешний вызов проходитi+1
(13), а возвращаемое значение внутреннего вызова (undefined, в этом случае -1, потому чтоi++
,i
оценивается до 12, 13, очевидно), , но нет гарантии, что это всегда будет иметь место
Это сделало довольно ясным, что эти типы конструкций запускают UB для всех функций.
Беспокойство Википедии
OP Цитирует это:
После действия, связанного с спецификатором формата преобразования ввода/вывода. Например, в выражении printf ( "foo% n% d", & a, 42) существует точка последовательности после вычисления% n перед печатью 42.
Затем применяет его к своему фрагменту (prinf("%d - %d - %d\n", i, your_function(++i, ++i), i);
), преследуя спецификаторы формата, чтобы служить точками последовательности.
То, о чем говорится в описании "спецификатор формата ввода/вывода", является спецификатором %n
. Соответствующий аргумент должен быть указателем на целое число без знака, и ему будет присвоено количество символов, напечатанных до сих пор. Естественно, %n
должен быть оценен до того, как остальные аргументы будут напечатаны. Однако использование указателя, переданного для %n
в других аргументах, по-прежнему опасно: он не UB (ну, это не так, но это может быть):
printf("Foo %n %*s\n", &a, 100-a, "Bar");//DANGER!!
Перед вызовом функции есть точка последовательности, поэтому выражение 100-a
будет оценено до того, как %n
установит &a
на правильное значение. Если a
неинициализирован, то 100-a
- UB. Если a
инициализируется равным 0, например, результат выражения будет равен 100. В целом, однако, этот вид кода в значительной степени требует неприятностей. Рассматривайте это как очень плохую практику, или хуже...
Просто посмотрите на результат, сгенерированный одним из следующих операторов:
unsigned int a = 90;
printf("%u %n %*s\n",a, &a, 10, "Bar");//90 Bar
printf("%u\n", a);//3
printf("Foo %u %n %*s\n",a, &a, 10-a, "Bar");//Foo 3 Bar < padding used: 10 - 3, not 10 - 6
printf("%u\n", a);//6
Как видите, n
переназначается внутри printf
, поэтому вы не можете использовать его новое значение в списке аргументов (потому что есть точка последовательности). Если вы ожидаете, что n
будет переназначен "на месте", вы, по сути, ожидаете, что C выскочит из вызова функции, оценит другие аргументы и вернется обратно в вызов. Это просто невозможно. Если вы должны были изменить unsigned int a = 90;
на unsigned int a;
, то поведение undefined.
Что касается 12
'
Теперь, когда OP считывает точки последовательности, он правильно замечает, что это утверждение:
printf("%d - %d - %d\n", i, your_function(++i, ++i), i);
Немного отличается: your_function(++i, ++i)
есть точка последовательности, а гарантирует, что i
будет увеличиваться дважды. Этот вызов функции является точкой последовательности, потому что:
Прежде чем функция будет введена в вызов функции. Порядок, в котором оцениваются аргументы, не указан, но эта точка последовательности означает, что все их побочные эффекты завершены до ввода функции
Это означает, что перед вызовом printf
your_function
должен быть вызван (поскольку его возвращаемое значение является одним из аргументов для вызова printf
) и i
будет увеличиваться в два раза.
Этот мог объяснять, что результат "12 - 0 - 12", но гарантированно ли это выход?
Нет
Технически, хотя большинство компиляторов сначала оценит вызов your_function(++i, ++i);
, стандарт позволит компилятору оценить аргументы, переданные в sprintf
слева направо (порядок не указывается после всех). Таким образом, это будет равноправный результат:
10 - 0 - 12
//or even
12 - 0 - 10
//and
10 - 0 - 10
//technically, even this would be valid
12 - 0 - 11
Хотя последний выход крайне маловероятен (это было бы очень неэффективно)