C / C ++ volatile имеет очень узкий диапазон гарантированного использования: для прямого взаимодействия с внешним миром (обработчики сигналов, написанные на C / C ++, находятся «снаружи», когда они вызываются асинхронно); вот почему доступ к изменчивым объектам определяется как наблюдаемые, как и консольный ввод-вывод и значение выхода программы (возвращаемое значение main).
Чтобы увидеть это, представьте, что любой изменчивый доступ фактически транслируется посредством ввода-вывода на специальной консоли, терминале или паре устройств FIFO с именами Доступы и Значения, где :
- непостоянная запись
x = v;
в объект x типа T преобразуется в запись в FIFO Доступов порядка записи, указанного как 4-кратный ("write", T, &x, v)
- непостоянное чтение (преобразование lvalue в rvalue)
x
преобразуется в запись в Доступ трехэтапного ("read", T, &x)
и ожидание значения на Значения.
Таким образом, volatile в точности похож на интерактивную консоль.
Хорошей спецификацией volatile является семантика ptrace (которую никто, кроме меня, не использует, но она по-прежнему является самой хорошей изменчивой спецификацией на свете):
- изменчивая переменная может быть исследована отладчиком / ptrace после того, как программа была остановлена в четко определенной точке;
- любой доступ к изменчивому объекту представляет собой набор четко определенных точек ПК (счетчика программ), так что там может быть установлена точка останова (**): выражение, выполняющее изменчивый доступ, преобразуется в набор адресов в коде, где нарушение вызывает разрыв в определенное выражение C / C ++;
- состояние любого изменчивого объекта может быть изменено произвольным образом (*) с помощью ptrace, когда программа остановлена, ограничиваясь только допустимыми значениями объекта в C / C ++; изменение битового шаблона изменчивого объекта с помощью ptrace эквивалентно добавлению выражения присваивания в C / C ++ в четко определенной точке останова C / C ++, поэтому это эквивалентно изменению исходного кода C / C ++ во время выполнения.
Это означает, что у вас есть четко определенное наблюдаемое состояние ptrace изменчивых объектов в этих точках, точка.
(*) Но вы не можете установить для изменчивого объекта недопустимый битовый шаблон с помощью ptrace: компилятор может предположить, что любой объект имеет допустимый битовый шаблон, как определено ABI. Любое использование ptrace для доступа к изменчивому состоянию должно соответствовать спецификации ABI для объектов, совместно используемых с отдельно скомпилированным кодом. Например, компилятор может предположить, что объект изменчивого числа не имеет отрицательного нулевого значения, если ABI этого не допускает. (Очевидно, что отрицательный ноль является допустимым состоянием, семантически отличным от положительного нуля для IEEE float.)
(**) Встраивание и развертывание цикла может генерировать множество точек в ассемблерном / двоичном коде, соответствующих уникальной точке C / C ++; Отладчики справляются с этим, устанавливая множество точек останова на уровне ПК для одной точки останова на уровне источника.
Семантика ptrace даже не подразумевает, что изменчивая локальная переменная хранится в стеке, а не в регистре; это означает, что расположение переменной, как описано в данных отладки, может быть изменено либо в адресуемой памяти через ее стабильный адрес в стеке (очевидно, стабильный на время вызова функции), либо в представлении сохраненных регистров приостановленная программа, которая находится во временной полной копии регистров, сохраненных планировщиком, когда поток выполнения приостановлен.
[На практике все компиляторы обеспечивают более надежную гарантию, чем семантика ptrace: все изменчивые объекты имеют стабильный адрес, даже если их адрес никогда не используется в коде C / C ++; эта гарантия иногда бывает бесполезной и строго пессимистичной. Более легкая семантическая гарантия ptrace сама по себе чрезвычайно полезна для автоматической переменной в регистре в "сборке высокого уровня".]
Вы не можете проверить работающую программу (или поток), не остановив ее; вы не можете наблюдать ни с одного процессора без синхронизации (такую синхронизацию обеспечивает ptrace).
Эти гарантии действуют на любом уровне оптимизации. При минимальной оптимизации все переменные фактически изменчивы, и программа может быть остановлена при любом выражении.
На более высоком уровне оптимизации вычисления сокращаются, и переменные могут быть даже оптимизированы, если они не содержат полезной информации для какого-либо законного запуска; наиболее очевидным случаем является «квазиконстантная» переменная, которая не объявляется как константа, но используется a-if const: устанавливается один раз и никогда не изменяется. Такая переменная не несет информации во время выполнения, если выражение, которое использовалось для ее установки, может быть пересчитано позже.
Многие переменные, которые несут полезную информацию, по-прежнему имеют ограниченный диапазон: если в программе нет выражения, которое может установить целочисленный тип со знаком для математического отрицательного результата (результат действительно отрицательный, а не отрицательный из-за переполнения в системе с двумя дополнениями) ) компилятор может предположить, что у них нет отрицательных значений. Любая попытка установить для них отрицательное значение в отладчике или через ptrace не будет поддерживаться, поскольку компилятор может сгенерировать код, который интегрирует предположение; создание изменчивого объекта заставит компилятор разрешить любое возможное допустимое значение для объекта, даже если в полном коде присутствуют только присвоения положительных значений (код во всех путях, которые могут получить доступ к этому объекту, в каждом TU (единице перевода) который может получить доступ к объекту).
Обратите внимание, что для любого объекта, который является общим за пределами набора коллективно транслируемого кода (все единицы TU, которые скомпилированы и оптимизированы вместе), ничего о возможных значениях объекта нельзя предполагать, кроме применимого ABI.
Ловушка (а не ловушка, как в вычислениях) состоит в том, чтобы ожидать семантики, подобной изменчивой Java, по крайней мере в одном процессоре, линейном, упорядоченном семантическом программировании (где по определению нет неупорядоченного выполнения, поскольку есть только POV в состоянии, один-единственный процессор):
int *volatile p = 0;
p = new int(1);
Не существует непостоянной гарантии, что p
может быть только нулевым или указывать на объект со значением 1: между инициализацией int
и установкой изменчивого объекта не подразумевается изменчивый порядок, поэтому обработчик асинхронного сигнала или точка останова на при назначении volatile может не отображаться int
инициализированный.
Но изменчивый указатель не может быть изменен спекулятивно: до тех пор, пока компилятор не получит гарантии, что выражение rhs (правая часть) не вызовет исключения (таким образом, оставит p
нетронутым), он не может изменить изменчивый объект (поскольку изменчивый доступ является наблюдаемый по определению).
Возвращаясь к вашему коду:
INTENABLE = 0; // volatile write (A)
my_var += 5; // normal write
INTENABLE = 1; // volatile write (B)
Здесь INTENABLE
является изменчивым, поэтому все обращения наблюдаемы; компилятор должен производить именно эти побочные эффекты; обычные записи являются внутренними по отношению к абстрактной машине, и компилятору нужно только сохранить эти побочные эффекты WRT для получения правильного результата, без учета каких-либо сигналов, которые находятся за пределами абстрактной семантики C / C ++.
С точки зрения семантики ptrace, вы можете установить точку останова в точках (A) и (B) и наблюдать или изменять значение INTENABLE
, но это все. Хотя my_var
не может быть полностью оптимизирован, поскольку он доступен для внешнего кода (кода передачи сигнала), но в этой функции нет ничего другого, что могло бы получить к нему доступ, поэтому конкретное представление my_var
не должно соответствовать его значение согласно абстрактной машине в этот момент.
Другое дело, если у вас есть вызов действительно внешней (не анализируемой компилятором, за пределами «коллективно транслируемого кода») ничего не делающей между ними:
INTENABLE = 0; // volatile write (A)
external_func_1(); // actual NOP be can access my_var
my_var += 5; // normal write
external_func_2(); // actual NOP be can access my_var
INTENABLE = 1; // volatile write (B)
Обратите внимание, что оба этих вызова внешних функций ничего не делают, возможно, ничего не делают:
external_func_1()
возможно, соблюдает предыдущее значение my_var
external_func_2()
возможно наблюдает новое значение my_var
Эти вызовы относятся к внешним, отдельно скомпилированным функциям NOP, которые должны выполняться в соответствии с ABI; таким образом, все глобально доступные объекты должны нести ABI-представление своего значения абстрактной машины: объекты должны достичь своего канонического состояния, в отличие от оптимизированного состояния, когда оптимизатор знает, что какое-то конкретное представление в памяти некоторых объектов не достигло ценность абстрактной машины.
В GCC такая внешняя функция, не выполняющая никаких действий, может быть записана либо asm("" : : : "memory");
, либо просто asm("");
. "memory"
неопределенно определен, но ясно означает "доступ ко всему в памяти, чей адрес был утрачен в глобальном масштабе".
[См. Здесь, я полагаюсь на прозрачное намерение спецификации, а не на ее слова, поскольку слова очень часто выбираются неправильно (#) и никем не используются для создания реализации в любом случае, и только мнение людей имеет значение, слов никогда не бывает.
(#) по крайней мере, в мире распространенных языков программирования, где люди не имеют квалификации для написания формальных или даже правильных спецификаций. ]
person
curiousguy
schedule
14.12.2018
my_var
вообще существует. - person Eugene Sh.   schedule 13.12.2018INTENABLE
на очистку / установку глобальной маски прерывания, что ли? - person Lundin   schedule 13.12.2018INTENABLE
должен быть своего рода глобальной маской прерывания, это то, что я имел в виду. Предположим,INTENABLE
- это регистр микроконтроллера, который разрешает / запрещает прерывания. Извините, если это было непонятно - person tlongeri   schedule 13.12.2018my_var
каким-то образом имеет отношение к коду, меня беспокоит то, что он не изменяется между чтением и записью операции+=
(или, ну, что наблюдаемое поведение в том, что между ними ничего не изменилось). - person tlongeri   schedule 13.12.2018volatile
вообще не гарантирует атомарность! Что ж, в любом случае такие вещи оставлены на усмотрение реализации. Но стандарт не гарантирует этого. - person Antti Haapala   schedule 13.12.2018volatile
. - person Eugene Sh.   schedule 14.12.2018__asm volatile ("cpsie i" : : : "memory");
, поэтому я информирую себя о том, что означают ключевое слово volatile и"memory"
в этом контексте. - person tlongeri   schedule 14.12.2018"memory"
плохо определен, но интуитивно это означает вызов отдельно скомпилированной функции, которая может получить доступ к любому глобально доступному объекту; он не может получить доступ к локальной переменной, которая не используется совместно с глобальным состоянием. - person curiousguy   schedule 14.12.2018