2016-01-06 3 views
11

Я прочитал here, что существует точка последовательности:очки в Printf

После действия, связанного с форматом преобразования ввода/вывода спецификатора. Например, в выражении printf("foo %n %d", &a, 42) имеется точка последовательности после оценки %n перед печатью 42.

Однако, когда я бегу this code:

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 дает тот же неожиданный результат)?

EDIT:

Я понимаю, что порядок аргументы your_function вычисляются и присваиваются параметрам не определен. Я не спрашиваю, почему мой средний срок равен 0. Я спрашиваю, почему два других условия оба 12.

+7

Согласно [это] (http://stackoverflow.com/questions/ 4176328/undefined-behavior-and-sequence-points) это неопределенное поведение. Я также считаю, что 'your_function (++ i, ++ i)' также является неопределенным поведением. – NathanOliver

+3

'your_function (++ i, ++ i)' явно UB. –

+0

Последовательность точек, описанных стандартной цитатой, находится внутри тела 'printf()' после того, как она была вызвана. У вас есть экстремальное неопределенное поведение в вызывающей последовательности перед вызовом функции 'printf()', что означает, что любой результат является приемлемым (включая тот, который вы получили). –

ответ

6

Поскольку этот вопрос был задан из комментария на основе обсуждения here, я приведу некоторый контекст:

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

ОП принимает и понимает это. Нет смысла повторять тот факт, что your_function(++i, ++i) является UB.

В ответ на это замечание: Благодаря Вашему комментарию, я вижу, что printf может быть оценен в любом порядке, но я понял, что быть потому printf аргументами являются частью va_list. Вы говорите, что аргументы любой функции выполняются в произвольном порядке?

ОП просят разъяснить, поэтому я разработал немного:

Второй комментарий: Да, это именно то, что я говорю. даже вызов int your_function(int a, int b) { return a - b; } не гарантирует, что переданные вами выражения будут оцениваться слева направо.Нет точки последовательности (точка, в которой выполняются все побочные эффекты предыдущих оценок). Возьмите this example. Вложенный вызов является точкой последовательности, поэтому внешний вызов проходит i+1 (13) и возвращаемое значение внутреннего вызова (неопределенный, в данном случае -1, потому что i++, i оценивает 12, 13, по-видимому), , но нет что это всегда будет иметь место

Это совершенно ясно, что эти конструкции позволяют запускать UB для всех функций.


Википедия спутанность

OP Котировки это:

После действия, связанного с форматом преобразования ввода/вывода спецификатора. Например, в выражении Е («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, например, результат выражения будет равен. В целом, этот код в основном вызывает проблемы. Рассматривайте это как очень плохую практика или хуже ...
Просто смотрите на выходе генерируется либо одной из этих утверждений:

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;, тогда поведение не определено.


О 12 «ы

Теперь, потому что ОП читать на точках последовательности, он правильно замечает, что это заявление:

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 

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

11

Я думаю, вы неправильно поняли текст о точках последовательности (SP) printf. Они как-то аномалии и только с %n, потому что этот спецификатор формата имеет побочные эффекты, и эти побочные эффекты необходимо упорядочить.

Во всяком случае, есть SP в начале выполнения printf() и после оценки всех аргументов. Этими спецификаторами формата являются после, поэтому они не влияют на вашу проблему.

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

+0

В основном исправлены, но «ни один из них не разделен точками последовательности» не совсем точен. В частности, существует точка последовательности после того, как аргументы 'your_function()' (и «обозначение функции») оцениваются и перед вызовом функции. Таким образом, оба приращения завершены до вызова этой функции. То, что вы не можете сказать, это когда другие аргументы 'printf()' были оценены w.r.t на вызов 'your_function()' - это не определено, поэтому значения, переданные 'printf()', не определены. –

+0

@JonathanLeffler: Поскольку все аргументы как 'printf()', так и 'your_function()' могут быть оценены без промежуточного SP, я бы сказал, что «ни один из них не разделен с SP» более или менее точен (должен ли я скажем, «секвенированы», «разделенные»?). Точка SP, на которую вы указываете, фактически выполняет вызовы 'your_function()' и 'printf()'. – rodrigo

+0

Сорта. Это грязно. Напротив, точка последовательности после аргументов 'your_function()' вычисляется и перед вызовом функции. Поэтому третий аргумент 'printf()' не может быть оценен до тех пор, пока не вернется вызов 'your_function()'; и поэтому вызов должен предшествовать SP перед вызовом 'printf()'. Но в остальном нет ограничений на порядок оценки аргументов 'printf()'. На этом этапе эти мелочи не имеют большого значения - общее поведение UB связано с двумя приращениями к 'i', и все может произойти в результате UB. –

4

Приняв четкий ответ на этот вопрос, строго выполняется (даже предотвращается) правилами С по порядку оценки и UB.

Указанные правила о порядке оценки изложены здесь:

C99 раздел 6.7.9, р23: 23 оценки списка инициализации выражения неопределенно секвенировали по отношению друг к другу и таким образом порядок, в котором происходят какие-либо побочные эффекты, неуточнен.

И this function call will exhibit undefined behavior:

your_function(++i, ++i) 

Из-UB, в сочетании с правилами о порядке оценки, точные прогнозы по ожидаемым результатам для следующих функций:

printf("%d - %d - %d\n", i, your_function(++i, ++i), i); 

являются невозможно.

Редактировать
... Я не спрашиваю о том, почему мой средний член равен 0. Я спрашиваю, почему два других условия являются оба 12.

Там нет никакой гарантии, какой из трех аргументов указанной функции вызывается первым. (из-за правил C по порядку оценки). И если средняя функция сначала оценивается, то в этом пункте вы вызвали Undefined Behavior. Кто может действительно сказать , почему два других условия - 12 ?. Потому что то, что происходит с i, когда оценивается второй аргумент, - это чья-то догадка.

+0

Я отредактировал вопрос, чтобы дать пояснения: «Я не спрашиваю, почему мой средний срок равен 0. Я спрашиваю, почему два других условия равны 12.» –

+0

@JonathanMee - Из-за правил по порядку оценки средний аргумент можно оценить сначала, в результате получив UB, по крайней мере, для себя ('your_function()'), и, возможно, с последующим воздействием на последующие результаты. Так что не исключено, что UB _ исчерпал всю функцию: 'printf ("% d -% d -% d \ n ", i, your_function (++ i, ++ i), i);'? – ryyker

+0

Ваше утверждение «буквально все может случиться» неверно. Там * есть * точка последовательности при возврате функции, поэтому после вызова 'your_function',' i' * будет * 12. Этот вопрос касался утверждения о http: //en.wikipedia.org означает, что я могу зависеть от аргументов 'printf', вызываемых по порядку. –

Смежные вопросы