Каков правильный ответ для cout << a++ << a;?

Недавно в одном из интервью возник следующий вопрос объективного типа.

int a = 0;
cout << a++ << a;

ответы:

а. 10
б. 01
с. неопределенное поведение

Я ответил на вариант b, то есть вывод будет "01".

Но к моему удивлению позже интервьюер сказал мне, что правильный ответ - вариант c: undefined.

Теперь я знаю концепцию точек последовательности в C++. Поведение не определено для следующего оператора:

int i = 0;
i += i++ + i++;

но согласно моему пониманию для утверждения cout << a++ << a, ostream.operator<<() будет вызван дважды, сначала с ostream.operator<<(a++) и позже ostream.operator<<(a),

Я также проверил результат на компиляторе VS2010, и его вывод также '01'.

4 ответа

Решение

Вы можете думать о:

cout << a++ << a;

Как:

std::operator<<(std::operator<<(std::cout, a++), a);

C++ гарантирует, что все побочные эффекты предыдущих оценок были выполнены в точках последовательности. Между оценками аргументов функции нет точек последовательности, что означает, что аргумент a можно оценить перед аргументом std::operator<<(std::cout, a++) или после. Таким образом, результат вышеописанного не определен.


C++17 обновление

В C++17 правила были обновлены. Особенно:

В выражении оператора сдвига E1<<E2 а также E1>>E2, каждое значение вычисления и побочный эффект E1 последовательность перед каждым вычислением значения и побочным эффектом E2,

Это означает, что для получения результата требуется код bкакие выводы 01,

См. P0145R3 Порядок уточнения выражений для Idiomatic C++ для получения более подробной информации.

Технически, в целом, это неопределенное поведение.

Но есть два важных аспекта ответа.

Кодовое утверждение:

std::cout << a++ << a;

оценивается как:

std::operator<<(std::operator<<(std::cout, a++), a);

Стандарт не определяет порядок оценки аргументов функции.
Так что либо:

  • std::operator<<(std::cout, a++) оценивается первым или
  • a оценивается первым или
  • это может быть любой определенный порядок реализации.

Этот порядок не указан [Ссылка 1] согласно стандарту.

[Ссылка 1] C++ 03 5.2.2 Вызов функции
Параграф 8

Порядок оценки аргументов не уточняется. Все побочные эффекты при оценке выражений аргументов вступают в силу до ввода функции. Порядок вычисления выражения postfix и списка выражений аргументов не определен.

Кроме того, между оценкой аргументов функции нет точки последовательности, но точка последовательности существует только после оценки всех аргументов [Ссылка 2].

[Ссылка 2] C++ 03 1.9 Выполнение программы [intro.execution]:
Параграф 17:

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

Обратите внимание, что здесь значение c к нему обращаются более одного раза без промежуточной точки последовательности, относительно этого стандарт говорит:

[Ссылка 3] C++ 03 5 Выражения [expr]:
Параграф 4:

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

Код модифицирует c более одного раза без промежуточной точки последовательности, и к ней не обращаются для определения значения хранимого объекта. Это явное нарушение вышеприведенного пункта, и, следовательно, результатом, установленным стандартом, является неопределенное поведение [Ссылка 3].

Точки последовательности определяют только частичное упорядочение. В вашем случае у вас есть (после разрешения перегрузки):

std::cout.operator<<( a++ ).operator<<( a );

Существует точка последовательности между a++ и первый звонокstd::ostream::operator<<и есть точка последовательности между вторым a и второй звонок std::ostream::operator<<, но между последовательностью нет a++ а также a; единственные ограничения порядка a++ быть полностью оценены (включая побочные эффекты) перед первым вызовом operator<<и что второе a быть полностью оцененным до второго вызова operator<<, (Существуют также причинно-следственные ограничения: второй вызов operator<< не может предшествовать первому, так как он требует результатов первого в качестве аргумента.) §5/4 (C++03) гласит:

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

Один из допустимых порядков вашего выражения a++, aпервый звонок operator<<Второй звонок operator<<; это изменяет сохраненное значение a (a++) и получает к нему доступ, кроме как для определения нового значения (второе a), поведение не определено.

Правильный ответ на вопрос. Это утверждение неприемлемо, потому что читатель не может видеть четкий ответ. Другой способ взглянуть на это состоит в том, что мы ввели побочные эффекты (C++), которые значительно затрудняют интерпретацию этого утверждения. Краткий код - это здорово, если он понятен.

Другие вопросы по тегам