"volatile" переопределения классификатора и компилятора - PullRequest
14 голосов
/ 29 марта 2010

Компилятор не может исключить или изменить порядок чтения / записи в volatile -качественные переменные.

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

Сценарий 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;

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

Ответы [ 4 ]

12 голосов
/ 29 марта 2010

Стандарт C ++ гласит (1.9 / 6):

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

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

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

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

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

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

4 голосов
/ 29 марта 2010

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

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

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

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

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

...

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

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

Вот немного из того, что Херб Саттер говорит о требуемом поведении volatile обращений в C / C ++ (из "volatile против volatile" http://www.ddj.com/hpc-high-performance-computing/212701484):

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

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

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

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

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

2 голосов
/ 29 марта 2010

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

0 голосов
/ 08 июля 2016

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

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

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

ИМХО, это область, которой действительно должен уделить внимание Стандарт, поскольку соответствующие концепции применимы ко многим платформам, и любая платформа, где ограждения не имеют смысла, может просто их игнорировать.

...