Если доступ к члену структуры, отмеченному как volatile, из прерывания, нужно ли помечать всю цепочку доступа как volatile?
Например
struct bar
{
volatile uint16_t a;
volatile uint16_t b;
};
struct foo
{
struct bar bar1;
struct bar bar2;
};
struct foo foo_arr[10];
void interrupt_handler(void)
{
struct bar *bar = &foo_arr[0].bar1;
bar->a = 2;
bar->b = 3;
}
Достаточно ли этого, чтобы участники a и b были отмечены как volatile?
Не дубликат, а наоборот.





Обычно бывает проблематично указать квалификаторы для отдельных членов структуры, а не для объекта структуры. В этом случае, поскольку каждый отдельный член является volatile, вы могли бы также сделать volatile struct bar bar1, разрешив существование экземпляров структуры, не являющихся volatile.
Однако в случае, если a и b являются регистрами ЦП, отображенными в памяти, не существует ситуации, в которой бы они не были volatile, поэтому, квалифицируя отдельные элементы как таковые, вы предотвращаете любое другое их использование, что в данном конкретном случае хорошо. сценарий. Однако создание структуры volatile не повредит.
Что касается использования этих переменных, единственное, что имеет значение, — это тип, используемый для «доступа к значению lvalue» объекта-члена, который должен осуществляться через правильно определенный тип. Например, * (int*)&bar->a вызовет неопределенное поведение — то же самое, если вы использовали const в объявлении.
Компилятор также не может ничего предполагать об этой структуре, он должен воспринимать ее как volatile во всех отношениях. Однако это было бы гораздо яснее и самодокументируемым для программиста, если бы это было явно объявлено volatile на каждом шагу. Не объявлять что-либо volatile, когда намерение состоит в том, чтобы относиться к этому как к volatile, — это просто плохой стиль программирования. Но что касается языка C, в этом нет необходимости.
Вы можете легко это проверить: https://godbolt.org/z/rxqPe8abx
Закомментируйте volatile, и компилятор (порт x86_64) оптимизирует записи в одну 32-битную запись, которая не будет соответствовать двум изменчивым переменным. С помощью volatile мы получаем две отдельные 16-битные записи, что требуется правилами выполнения программы C («абстрактная машина»).
Значит, компилятор будет рассматривать структуры и даже массив как изменчивые из-за двух членов?
@Fredrik Не существует определенного поведения для «изменчивой структуры» или «изменчивого массива». При использовании в структуре это просто ярлык для «все скалярные члены являются изменчивыми», но назначения на уровне структуры и т. д. не имеют определенного порядка операций. Кроме того, он не передается должным образом, поэтому аннотирование его в структуре структур может привести к появлению неожиданных энергонезависимых членов.
@Fredrik Да, это то, что означает «компилятор не может ничего предполагать об этой структуре», поскольку он не может использовать какие-либо ярлыки или оптимизировать ее.
Поставьте себя на место компилятора.
Сначала давайте разложим выражение &foo_arr[0].bar1 синтаксически, в порядке старшинства:
foo_arr — имя вашего массиваfoo_arr[0] получает доступ к смещению (0) и извлекает значениеfoo_arr[0].bar1 получает доступ к фиксированному смещению внутри элемента массива и извлекает значение&foo_arr[0].bar1 возвращает адрес последнего доступного значенияВ этот момент у вас есть локальный указатель bar. На этом пути ничего не отмечено volatile и оно вам там не нужно. Сам массив выделяется статически и его расположение не может внезапно измениться. Это статический макет размера 10 * sizeof(struct foo).
Выражения bar->a и bar->b используются, когда вы действительно получаете доступ к данным в этой области. Компилятор сгенерирует код для разыменования указателя, а затем получит доступ к фиксированному смещению оттуда. Поскольку члены a и b отмечены volatile, с помощью выражения должна быть сгенерирована инструкция доступа к памяти.
Меня больше интересует волатильность
foo_arr[0].bar1 = foo_arr[0].bar2;.