Вывод printf (%d %d, c++, c); тоже неопределенный?

Недавно я наткнулся на сообщение Каков правильный ответ для cout ‹‹ c++ ‹‹ c;? и задавался вопросом, будет ли вывод

int c = 0;  
printf ("%d %d", c++, c);  

также не определен??

Я изучал в лекциях, что постфиксные и префиксные операторы увеличивают значение только после получения точки с запятой. Так что, по-моему, вывод 0 0 правильный!!!


person Snehasish    schedule 01.06.2012    source источник
comment
возможный дубликат несколько "++", работающих с переменными и указателями   -  person Pascal Cuoq    schedule 17.08.2013


Ответы (6)


Я изучал в лекциях, что постфиксные и префиксные операторы увеличивают значение только после получения точки с запятой.

Пришлите ко мне своего лектора, чтобы я отнесла ему бейсбольную биту вежливо указала на его ошибку.

Когда применяется побочный эффект пре- или постфикса ++ и --, он не указан, за исключением требования, чтобы это произошло до следующей точки последовательности. В выражении вроде

x = a++ * b

a может быть обновлено сразу после того, как a++ будет оценено, или обновление может быть отложено до тех пор, пока a++ * b не будет оценено и результат не будет присвоен x, или где-то между ними.

Вот почему такие выражения, как i++ * i++, printf("%d %d", c++, c) и a[i++] = i, а также множество других, являются плохим juju. Вы получите разные результаты в зависимости от компилятора, настроек оптимизации, окружающего кода и т. д. Стандарт языка явно оставляет поведение неопределенным, так что компилятор не обязан "делать правильные вещи", что бы то ни было. может быть и правильно. Помните, что определение неопределенного поведения таково:

3.4.3

1 undefined behavior
behavior, upon use of a nonportable or erroneous program construct or of erroneous data, for which this International Standard imposes no requirements

2 NOTE Possible undefined behavior ranges from ignoring the situation completely with unpredictable results, to behaving during translation or program execution in a documented manner characteristic of the environment (with or without the issuance of a diagnostic message), to terminating a translation or execution (with the issuance of a diagnostic message).

3 EXAMPLE An example of undefined behavior is the behavior on integer overflow.

Это преднамеренное дизайнерское решение — причина того, что порядок этих операций не указан, состоит в том, чтобы дать реализации возможность изменять порядок вычислений в целях оптимизации. Однако в обмен на эту свободу некоторые операции не будут иметь четко определенных результатов.

Обратите внимание, что компилятор может попытаться обнаружить эти случаи и выдать диагностику; printf("%d %d", c++, c); было бы достаточно легко поймать, но в общем случае это было бы ошибкой для обнаружения. Представьте, если бы это было написано printf("%d %d", (*p)++, c); если p указывает на c, то поведение не определено, в противном случае все в порядке. Если p назначено в другой единице перевода, то во время компиляции невозможно узнать, является ли это проблемой или нет.

Эту концепцию нетрудно понять, но она является одним из наиболее часто неправильно понимаемых (и неправильно изучаемых) аспектов языка C. Несомненно, именно поэтому спецификации языков Java и C# устанавливают для всего определенный порядок вычислений (все операнды оцениваются слева направо, а все побочные эффекты применяются немедленно).

person John Bode    schedule 01.06.2012
comment
Хороший ответ - за исключением того, что вы не цитируете раздел стандарта, в котором говорится, что он не определен: между предыдущей и следующей точкой последовательности сохраненное значение объекта должно быть изменено не более одного раза путем оценки выражения. Кроме того, предыдущее значение должно считываться только для определения сохраняемого значения. - person Keith Thompson; 17.08.2013

В лекциях я изучал, что постфиксные и префиксные операторы увеличивают значение только после получения точки с запятой.

Стандарт не описывает это. точка последовательности — это точка в коде, в которой побочные эффекты, которые могли возникнуть в предыдущих частях код был оценен. Запятая между аргументами функции не является точкой последовательности, поэтому поведение здесь не определено.

Порядок вычисления аргументов функции не указан. Нет гарантии, что аргументы функции будут оцениваться в порядке (1, 2, N), поэтому нет гарантии, что приращение будет оцениваться до того, как будет передан второй аргумент.

Так что, по-моему, вывод 0 0 правильный!!!

Нет, поведение не определено, поэтому вы не можете обоснованно утверждать, что на выходе будет 0 0.

person Ed S.    schedule 01.06.2012
comment
Я не знал, что порядок вычисления аргументов функции не определен!!! Спасибо за обновление моих знаний!! - person Snehasish; 01.06.2012
comment
Однако, В статье Википедии написано, что ОПЕРАТОР ЗАПЯТОЙ ВВОДИТ ТОЧКУ ПОСЛЕДОВАТЕЛЬНОСТИ!!! в отличие от того, что вы сказали !! - person Snehasish; 01.06.2012
comment
@snehasish: ты недостаточно внимательно читал. Это не пример оператора запятой (который является точкой последовательности), и порядок вычисления аргументов функции не указан. - person Ed S.; 01.06.2012
comment
@R.. Ну, поведение не указано в отношении порядка оценки и не определено в отношении чтения и обновления c в той же точке последовательности - person Shafik Yaghmour; 17.08.2013

Поведение программы не определено, потому что она нарушила требования 6.5 Expressions:

Между предыдущей и следующей точкой последовательности сохраненное значение объекта должно быть изменено не более одного раза путем вычисления выражения. Кроме того, предыдущее значение должно считываться только для определения сохраняемого значения.

c++ и c оцениваются без промежуточной точки последовательности, а предыдущее значение c считывается как для определения значения, которое должно быть сохранено c++, так и для определения значения выражения c.

person R.. GitHub STOP HELPING ICE    schedule 01.06.2012

Поведение будет определенно неопределенным из-за неопределенного порядка оценки параметров. Вы можете доказать этот «неопределенный вывод», выполнив случайное тестирование:

printf("%d %d\n", c++, c);
// result: 0 1
printf("%d %d %d\n", c++, c, c++);
// result: 1 2 0
printf("%d %d %d %d\n", c++, c++, c++, c);
// result: 2 1 0 3
printf("%d %d %d %d\n", c++, c, c++, c);
// result: 1 2 0 2
printf("%d %d %d %d\n", c++, c, c, c);
// result: 0 1 1 1
person juliomalegria    schedule 01.06.2012
comment
Поведение не указано из-за порядка оценки, но оно не определено из-за чтения предыдущего значения c для чтения предыдущего значения, а также для одновременного обновления в той же точке последовательности. Подобные тесты на самом деле не доказывают, что он не определен, только чтение стандарта может сказать вам об этом. - person Shafik Yaghmour; 18.08.2013

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

Между прочим, технически неверно, что приращение происходит только после точки с запятой. Что стандарт гарантирует, так это то, что приращение произойдет не позже точки с запятой. [На самом деле, в вашем случае, я считаю, что стандарт гарантирует, что это произойдет до того, как управление будет передано функции printf(), но теперь этот ответ начинает превращаться в область педантичных мелочей, так что позвольте мне оставить это дело там. !]

В любом случае, короче, вы правы. Поведение не определено.

Обновление: Как справедливо отмечает @R.., неопределенное поведение возникает из-за отсутствия точки последовательности между аргументами. Стандарт весьма осторожен в отношении причастий unspecified и undefined, поэтому исправление принимается с благодарностью.

person thb    schedule 01.06.2012
comment
Ваш ответ только объясняет, почему результат в лучшем случае не указан; неопределенное поведение возникает из-за отсутствия точки последовательности между аргументами. - person R.. GitHub STOP HELPING ICE; 01.06.2012
comment
Все эти разговоры о точках с запятой бессмысленны. Точки последовательности - это то, на что ссылается стандарт. - person Ed S.; 01.06.2012
comment
(Точка пустяка: я полагаю, что стандарт C++11 отказался от термина точка последовательности. См. разделы с 1.9.13 по .15.) - person thb; 01.06.2012
comment
@thb: Да, но этот вопрос помечен C. - person Ed S.; 01.06.2012
comment
Честно говоря, выбор имени переменной позволяет легко запутаться и подумать о другом языке... - person R.. GitHub STOP HELPING ICE; 01.06.2012
comment
@R..: Ха-ха, это меня немного рассмешило - person Ed S.; 01.06.2012

Эта программа демонстрирует сочетание как неопределенного поведения, так и неопределенное поведение. Начиная с неуказанного поведения, проект стандарта C99 в section6.5 параграфе 3 гласит:

Группировка операторов и операндов указывается синтаксисом.74) За исключением случаев, указанных ниже (для вызова функции (), &&, ||, ?: и операторов запятой), порядок вычисления подвыражений и порядок возникновения побочных эффектов не указан.

В нем также говорится, за исключением случаев, указанных ниже, и конкретно цитируется function-call (), поэтому мы видим, что позже в проекте стандарта в разделе 6.5.2.2 Вызовы функций параграф 10 говорится:

Порядок вычисления указателя функции, фактических аргументов и подвыражений в фактических аргументах не указан, но перед фактическим вызовом есть точка последовательности.

Таким образом, мы не знаем, произойдет ли сначала чтение C или оценка C++ в этой строке кода:

printf ("%d %d", c++, c); 

кроме того, в разделе 6.5.2.4 Операторы инкремента и декремента Postfix параграф 2 гласит:

[...] После получения результата значение операнда увеличивается. [...] Побочный эффект обновления сохраненного значения операнда возникает между предыдущим и следующая точка последовательности.

Итак, все, что мы знаем, это то, что при выполнении постинкремента c будет обновляться после того, как его значение будет прочитано, но перед следующей последовательностью точка, которая находится прямо перед вызовом printf, но не более того. Что касается неопределенного поведения, если мы посмотрим на раздел 6.5 параграф 2 из черновика стандарта, там говорится:

Между предыдущей и следующей точкой последовательности сохраненное значение объекта должно быть изменено не более одного раза путем вычисления выражения. Кроме того, предыдущее значение должно считываться только для определения сохраняемого значения.

В выражении printf считывается cs предыдущее значение, чтобы оценить как C++, так и C, и поэтому мы теперь находимся на территории undefined.

person Shafik Yaghmour    schedule 16.08.2013