Я читаю Вот что есть точка последовательности:
После действия, связанного со спецификатором формата преобразования ввода / вывода. Например, в выражении
printf("foo %n %d", &a, 42)
есть точка последовательности после%n
оценивается перед печатью42
,
Тем не менее, когда я бегу этот код:
int your_function(int a, int b) {
return a - b;
}
int main(void) {
int i = 10;
printf("%d - %d - %d\n", i, your_function(++i, ++i), i);
}
Вместо того, что я ожидаю, я получаю:
12 — 0 — 12
Это означает, что там было не точка последовательности, созданная для спецификатора формата преобразования. Является http://en.wikipedia.org неправильно, или я просто что-то неправильно понял, или gcc не совместим в этом случае (случайно Visual Studio 2015 дает тот же неожиданный результат)?
РЕДАКТИРОВАТЬ:
Я понимаю, что порядок аргументов your_function
оцениваются и присваиваются параметры не определены. Я не спрашиваю, почему мой средний термин равен 0. Я спрашиваю, почему два других термина — 12.
Потому что этот вопрос был задан из-за обсуждения на основе комментариев Вот, Я предоставлю некоторый контекст:
первый комментарий: Порядок операций не гарантированно будет порядок передачи аргументов функции. Некоторые люди (ошибочно) предполагают, что аргументы будут оцениваться справа налево, но согласно стандарту поведение не определено.
ОП принимает и понимает это. Нет смысла повторять тот факт, что your_function(++i, ++i)
это UB.
В ответ на этот комментарий: Благодаря вашему комментарию я вижу, что
printf
может оцениваться в любом порядке, но я так понял, потому чтоprintf
аргументы являются частьюva_list
, Вы говорите, что аргументы любой функции выполняются в произвольном порядке?
ОП просит разъяснений, поэтому я немного уточнил:
Второй комментарийДа, это именно то, что я говорю. даже звонит
int your_function(int a, int b) { return a - b; }
не гарантирует, что передаваемые вами выражения будут оцениваться слева направо. Там нет точки последовательности (точка, в которой выполняются все побочные эффекты предыдущих оценок). принимать этот пример. Вложенный вызов является точкой последовательности, поэтому внешний вызов проходитi+1
(13) и возвращаемое значение внутреннего вызова (не определено, в данном случае -1, потому чтоi++
,i
оценивает до 12, 13, по-видимому), но нет гарантии, что так будет всегда
Это ясно дало понять, что эти виды конструкций запускают UB для всех функций.
ОП цитирует это:
После действия, связанного со спецификатором формата преобразования ввода / вывода. Например, в выражении printf («foo% n% d», &а, 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;
тогда поведение не определено.
Теперь, потому что ОП читает точки последовательности, он правильно замечает, что это утверждение:
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
Хотя последний вывод крайне маловероятен (это было бы очень неэффективно)
Я думаю, что вы неправильно поняли текст о printf
последовательность точек (SP). Они как-то аномалия, и только с %n
потому что этот спецификатор формата имеет побочные эффекты, и эти побочные эффекты должны быть упорядочены.
Во всяком случае, есть SP в начале выполнения printf()
и после оценки всех аргументов. Эти спецификаторы формата SP все после этот, чтобы они не влияли на вашу проблему.
В вашем примере, использование i
все находятся в аргументах функции, и ни один из них не разделен точками последовательности. Поскольку вы изменяете значение (дважды) и используете его без промежуточных точек последовательности, ваш код — UB.
Какое правило о СП в printf
означает, что этот код хорошо сформирован:
int x;
printf("%d %n %d %n\n", 1, &x, 2, &x);
хотя значение x
модифицируется дважды.
Но этот код UB:
int x = 1;
printf("%d %d\n", x, ++x);
ПРИМЕЧАНИЕ: помните, что %n
означает, что количество написанных символов скопировано в целое число, указанное в соответствующем аргументе.
Достижение четкого ответа на этот вопрос сильно зависит (даже предотвращается) от правил C по порядку оценки и UB.
Указанные правила о порядке оценки изложены здесь:
C99 раздел 6.7.9, стр. 23: 23 Оценки списка инициализации
выражения неопределенно упорядочены по отношению друг к другу
и, следовательно, порядок возникновения побочных эффектов не определен.
А также, этот вызов функции покажет неопределенное поведение:
your_function(++i, ++i)
Из-за UB, в сочетании с правилами о порядке оценки, точные прогнозы ожидаемых результатов для следующих:
printf("%d - %d - %d\n", i, your_function(++i, ++i), i);
невозможно
редактировать
…Я не спрашиваю, почему мой средний термин равен 0. Я спрашиваю, почему два других термина — 12.
Нет никакой гарантии, какой из трех аргументов вышеуказанной функции будет вызван первым. (из-за правил C о порядке оценки). И если средняя функция будет оценена первой, то в таком случае ты призвал Неопределенное поведение . Кто на самом деле может сказать Зачем другие два условия 12 ?. Потому что то, что происходит с i
когда второй аргумент оценивается, это чья-то догадка.