"изменчивый" классификатор и переупорядочивание компилятора

Компилятор не может устранить или изменить порядок чтения/записи на volatile -qualified variables.

Но как насчет случаев, когда присутствуют другие переменные, которые могут быть или не быть volatile -qualified?

Сценарий 1

volatile int a;
volatile int b;

a = 1;
b = 2;
a = 3;
b = 4;

Может ли компилятор переупорядочить первый и второй, или третий и четвертый присвоения?

Сценарий 2

volatile int a;
int b, c;

b = 1;
a = 1;
c = b;
a = 3;

Тот же вопрос, может ли компилятор переупорядочить первый и второй, или третий и четвертый присвоения?

Ответ 1

В стандарте С++ говорится (1.9/6):

Наблюдаемое поведение абстрактная машина - это ее последовательность считывает и записывает данные волатильные и вызовы функций библиотечного ввода/вывода.

В сценарии 1 любое из предлагаемых вами изменений изменяет последовательность записи на изменчивые данные.

В сценарии 2 ни одно изменение, которое вы предлагаете, не изменяет последовательность. Таким образом, они разрешены в соответствии с правилом "как-если" (1.9/1):

... соответствующие реализации требуется для подражания (только) наблюдаемое поведение абстрактного машина...

Чтобы сказать, что это произошло, вам нужно будет изучить машинный код, использовать отладчик или спровоцировать undefined или неопределенное поведение, результат которого вам известен в вашей реализации. Например, реализация может сделать гарантии относительно представления, которое одновременно выполняет потоки одной и той же памяти, но выходит за рамки стандарта С++. Поэтому, хотя стандарт может разрешить конкретное преобразование кода, конкретная реализация может исключить его из-за того, что он не знает, будет ли ваш код запускаться в многопоточной программе.

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

Ответ 2

Для сценария 1 компилятор не должен выполнять какие-либо переупорядочивания, о которых вы упоминаете. Для сценария 2 ответ может зависеть от:

  • и видны ли переменные b и c вне текущей функции (либо нелокальными, либо имеющими свой адрес
  • с кем вы разговариваете (видимо, есть некоторые разногласия о том, как строка volatile находится в C/С++)
  • реализация вашего компилятора

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

из C99 5.1.2.3/2 "Выполнение программы":

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

...

(параграф 5) Наименьшие требования к соответствующей реализации:

  • В точках последовательности летучие объекты стабильны в том смысле, что предыдущие обращения завершены, а последующие обращения еще не произошли.

Вот немного того, что Герб Саттер должен сказать о требуемом поведении volatile доступа в C/С++ (от volatile vs. volatile " http://www.ddj.com/hpc-high-performance-computing/212701484):

как насчет близких обычных чтений и писем - могут ли они быть переупорядочены вокруг неопровержимых чтений и записей? Сегодня нет практического портативного ответа, потому что реализация компилятора C/С++ сильно различается и вряд ли сходится в ближайшее время. Например, одна интерпретация Стандарта С++ предполагает, что обычные чтения могут свободно перемещаться в любом направлении во время изменчивого чтения или записи C/С++, но что обычная запись не может перемещаться вообще во время нестабильной чтения или записи C/С++, которая сделает C/С++ летучим как менее ограничительным, так и более ограничительным, чем упорядоченный атом. Некоторые поставщики компиляторов поддерживают эту интерпретацию; другие не оптимизируют себя во всех изменчивых режимах чтения или записи; а третьи имеют свою собственную предпочтительную семантику.

И за что стоит, Microsoft документирует следующее для ключевого слова C/С++ volatile (как Microsoft-sepcific):

  • Запись в изменчивый объект (volatile write) имеет семантику Release; ссылка на глобальный или статический объект, который возникает до того, как запись в изменчивый объект в последовательности команд произойдет до этой волатильной записи в скомпилированном двоичном файле.

  • Чтение изменчивого объекта (volatile read) имеет семантику Acquire; ссылка на глобальный или статический объект, который возникает после считывания энергозависимой памяти в последовательности команд, произойдет после этого волатильного чтения в скомпилированном двоичном файле.

Это позволяет использовать изменчивые объекты для блокировок и выпусков памяти в многопоточных приложениях.

Ответ 3

Летучие - это не забор памяти. Назначения B и C в фрагменте № 2 могут быть устранены или выполняться всякий раз. Почему вы хотите, чтобы объявления в # 2 вызывали поведение # 1?

Ответ 4

Некоторые компиляторы рассматривают доступ к нестабильным объектам как забор памяти. Другие нет. Некоторые программы написаны так, что volatile работает как забор. Других нет.

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

Хорошим подходом может быть определение макроса semi_volatile как расширение до нуля на системах, где volatile подразумевает ограждение памяти, или volatile в системах, где это не так. Если переменные, которые должны иметь доступ, упорядоченные по отношению к другим переменным volatile, но не друг к другу, квалифицируются как semi-volatile, и этот макрос определен правильно, надежная работа будет достигнута на системах с или без ограждений памяти, и будет достигнута самая эффективная работа, которая может быть достигнута на системах с ограждениями. Если компилятор действительно реализует квалификатор, который работает по мере необходимости, semivolatile, он может быть определен как макрос, который использует этот определитель и обеспечивает еще лучший код.

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