Разрешается ли переупорядочивание хранилища правилом C++ «как если»?

Правило "как если бы" в основном определяет, какие преобразования разрешено выполнять реализации в допустимой программе на C++. Короче говоря, разрешены все преобразования, не влияющие на наблюдаемое поведение программы.

Что именно означает "наблюдаемое поведение", cppreference.com, по-видимому, имеет другое определение, чем то, которое дано в Стандарте, относительно ввода/вывода. Я не уверен, является ли это переосмыслением Стандарта или ошибкой.

Правило «как если бы» от cppreference.com:

  • Все операции ввода и вывода происходят в том же порядке и с тем же содержанием, как если бы программа выполнялась так, как она написана.

Правило «как если бы» стандарта:

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

Это различие важно для меня, потому что я хочу знать, является ли обычное переупорядочивание хранилища допустимой оптимизацией компилятора или нет. Согласно формулировке cppreference, хранилище памяти должно принадлежать output operations, о котором он упоминает. Но согласно Стандарту хранилище памяти не похоже на the output dynamics of interactive devices. (Что вообще такое интерактивные устройства?)

Пример для подражания.

int A = 0;
int B = 0;

void foo()
{
    A = B + 1;              // (1)
    B = 1;                  // (2)
}

Современный компилятор может сгенерировать следующий код для функции foo:

mov     0x804a018, %eax
movl    $0x1, 0x804a018    ; store 1 to B
add     $0x1, %eax         
mov     %eax, 0x804a01c    ; store 1 to A
ret

Как видно, магазин до A переупорядочен с магазином до B. Соответствует ли это правилу "как если бы"? Разрешается ли стандартом такое изменение порядка?


person Eric Z    schedule 14.08.2014    source источник
comment
(Что вообще такое интерактивные устройства?) Они подсказывали, что вывод фактически доставляется до того, как программа ожидает ввода. Консоль.   -  person curiousguy    schedule 10.06.2018


Ответы (2)


Если cppreference.com не согласен с фактическим текстом стандарта C++, cppreference.com неверен. Единственное, что может заменить текст стандарта, — это более новая версия стандарта и официальные решения отчетов об ошибках (которые иногда объединяются в документы, называемые "техническими исправлениями", что является причудливым названием для второстепенной версии стандарта).

Однако в этом случае вы неправильно поняли, что cppreference.com подразумевает под «операциями ввода и вывода». (Если мне не изменяет память, этот текст дословно взят из более старой версии стандарта.) Сохранение в памяти НЕ является операцией вывода. Только запись в файл (то есть любой выходной поток stdio.h или iostream или другой механизм, определяемый реализацией, например файловый дескриптор Unix) считается выводом для целей этого правила.

Стандарты C и C++ до их пересмотра в 2011 году предполагали однопоточную абстрактную машину и, следовательно, не удосужились указать что-либо о порядке хранения, поскольку не было возможности наблюдать за сохранением вне порядка программы. . C(++)11 добавил целую кучу правил для упорядочения хранения как часть новой спецификации многопоточности.

person zwol    schedule 14.08.2014
comment
+1 А как насчет int x, y; x = 3; x = 4; y = x; Если x = 3 переупорядочить с x = 4, то последний y = x будет наблюдать за ним даже на однопоточной абстрактной машине. Я знаю, что это глупо, но в соответствии с правилом как если бы это действительно независимо от того, что стандарт требует в отношении порядка модификации одного и того же места в памяти? - person Eric Z; 14.08.2014
comment
@EricZ, если y отправляется на устройство вывода (например, с std::cout << y;), правило «как если» может преобразовать этот код в эквивалент std::cout << 4; - person Cubbi; 14.08.2014
comment
@Cubbi Что мне любопытно, так это то, что порядок модификации не считается одним из правил observable behaviors правила как если бы. Таким образом, компилятор может переупорядочивать x = 3 с x = 4, что не имеет никакого смысла. Интересный? - person Eric Z; 14.08.2014
comment
@ Зак, нет, этой формулировки нет в более старой версии стандарта (проверено до C ++ 98 и C99). Хорошо, что это наконец заметили. - person Cubbi; 14.08.2014
comment
@EricZ наблюдаемое поведение - это содержимое выходного потока при завершении программы. Выполнение x=4 сначала изменит это. Если нет вывода, да, он может это сделать (бессмысленно) - person Cubbi; 14.08.2014
comment
@Cubbi В моем исходном случае он не записывается ни в какой выходной поток. Это всего лишь два хранилища в одном и том же месте памяти, которое не упоминается ни в одном из наблюдаемых поведений, верно? - person Eric Z; 14.08.2014
comment
Давайте продолжим обсуждение в чате. - person Eric Z; 14.08.2014
comment
@EricZ Чат — это ошибка, он уводит разговор от вопроса, которому он и принадлежит. - person zwol; 14.08.2014
comment
@EricZ int main(void) { int x, y; x = 3; x = 4; y = x; printf("%d %d\n", x, y); return 0; } должен напечатать 4 4 и успешно выйти. Правило «как если» означает не больше и не меньше того, что компилятор может выдать любую последовательность машинных инструкций, которая ему нравится, до тех пор, пока они имеют такие эффекты. В частности, замена двух хранилищ на x приведет к тому, что программа напечатает 3 3, что будет неверно. - person zwol; 14.08.2014
comment
@EricZ Однако int main(void) { int x, y; x = 3; x = 4; y = x; return 0; } не имеет наблюдаемых эффектов, зависящих либо от x, либо от y, поэтому компилятор может делать с ними все, что ему заблагорассудится: в частности, он может не генерировать код ни для чего, кроме return 0; что и произойдет с большинством современных компиляторов, если вы включите оптимизацию. - person zwol; 14.08.2014
comment
@Зак. Это имеет смысл. Что мне любопытно, так это то, как в большом проекте компилятор узнает, изменит ли изменение порядка двух хранилищ будущее наблюдательное поведение, которое появится в программе очень поздно? - person Eric Z; 14.08.2014
comment
@Zack Кроме того, в С++ 11 наблюдаемое поведение применяется ко всем потокам на абстрактной машине или к одному потоку? Я предполагаю, что он по-прежнему ограничен только одним потоком, потому что компилятор обычно не знает, изменит ли переупорядочение какое-либо наблюдаемое поведение другого потока. Верно? - person Eric Z; 14.08.2014
comment
@EricZ анализ потока данных — это общий термин, обозначающий, как компилятор знает. Под этим зонтиком находится множество различных алгоритмов. Общий принцип заключается в том, что компилятор выполняет операции в порядке программы, если он не может доказать, что изменение порядка не изменит наблюдаемое поведение. - person zwol; 14.08.2014
comment
@EricZ Правила для многопоточных программ слишком сложны, чтобы их можно было обобщить в этом поле для комментариев, и я боюсь, что сам не до конца их понимаю. См. раздел 5.1.2.4 документа N1570. . - person zwol; 14.08.2014
comment
@Zack Правило as-if не зависит от потоков (не зависит от модели памяти). Он ничего не говорит о потоках, просто program. Определение необходимой семантики для абстрактной машины зависит от базовой модели памяти. В многопоточной модели (C++11) существуют отношения между потоками, такие как synchronized-with (=> inter-thread happen-before). В этом случае наблюдаемое поведение естественным образом расширяется, чтобы охватить поведение других потоков. А поскольку компиляторы обычно мало знают о другом потоке, он не будет переупорядочивать доступ к памяти через границу inter-thread happens-before. - person Eric Z; 14.08.2014
comment
предполагается, что это однопоточная абстрактная машина Неверно, сигнал существовал всегда - person curiousguy; 10.06.2018
comment
@curiousguy Threading — это совершенно другая концепция, чем сигналы. Абстрактная машина C++98 была прерываемой, но в ней не было параллелизма, и стандарт изо всех сил старался не указывать, что может наблюдать обработчик сигнала; согласно абсолютной букве стандарта, выполнение всего в обработчике сигнала, кроме записи значения в volatile sig_atomic_t, имеет неопределенное поведение. Стандарты наложения, такие как POSIX, дают более сильные гарантии, и это влечет за собой введение слабого понятия порядка хранения, но в POSIX были потоки задолго до того, как это было в C++, поэтому он все равно был нужен. - person zwol; 10.06.2018
comment
@zwol Я имел в виду, что прерываемая модель — это модель, в которой возникает вопрос о том, насколько обработчик сигналов может взаимодействовать с памятью потока. (На практике любое взаимодействие через энергозависимый объект, который является атомарным на ЦП, допустимо.) Итак, возникает вопрос, можно ли переупорядочивать хранилища для энергозависимых переменных? и ответ они не могут указать в стандарте. Это похоже на очень плохое представление о потоках. - person curiousguy; 10.06.2018
comment
Тот факт, что атомарные примитивы могут использоваться как для связи между потоками, так и для обмена сигналами между потоками, показывает, что это сравнимые взаимодействия: это совсем не одно и то же, но у них есть общие черты. (Я не утверждаю, что сигнал заменяет многопоточность.) На линейной машине (все потоки привязаны к одной единице выполнения) volatile также может использоваться для некоторого ограниченного взаимодействия между потоками. - person curiousguy; 10.06.2018

Настоящая формулировка правила «как если бы» находится в §1.9/8 стандарта:

  • Доступ к volatile-объектам оценивается строго по правилам абстрактной машины.
  • При завершении программы все данные, записанные в файлы, должны быть идентичны одному из возможных результатов, которые могли бы быть получены при выполнении программы в соответствии с абстрактной семантикой.
  • Динамика ввода и вывода интерактивных устройств должна происходить таким образом, чтобы подсказка вывода фактически доставлялась до того, как программа ожидает ввода. То, что представляет собой интерактивное устройство, определяется реализацией.

Поскольку A и B не являются изменчивыми, такое переупорядочивание разрешено.

person Brian Bi    schedule 14.08.2014