Очки последовательности в printf

Я читаю Вот что есть точка последовательности:

После действия, связанного со спецификатором формата преобразования ввода / вывода. Например, в выражении 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.

11

Решение

Потому что этот вопрос был задан из-за обсуждения на основе комментариев Вот, Я предоставлю некоторый контекст:

первый комментарий: Порядок операций не гарантированно будет порядок передачи аргументов функции. Некоторые люди (ошибочно) предполагают, что аргументы будут оцениваться справа налево, но согласно стандарту поведение не определено.

ОП принимает и понимает это. Нет смысла повторять тот факт, что 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

Хотя последний вывод крайне маловероятен (это было бы очень неэффективно)

6

Другие решения

Я думаю, что вы неправильно поняли текст о 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 означает, что количество написанных символов скопировано в целое число, указанное в соответствующем аргументе.

11

Достижение четкого ответа на этот вопрос сильно зависит (даже предотвращается) от правил 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 когда второй аргумент оценивается, это чья-то догадка.

4
По вопросам рекламы [email protected]