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

StackOverflow https://stackoverflow.com/questions/2535148

Вопрос

Компилятор не может исключить или изменить порядок операций чтения/записи в 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;

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

Это было полезно?

Решение

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

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

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

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

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

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

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

Другие советы

В сценарии 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):

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

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

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

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

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

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

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top