Разрешено ли компилятору C объединять последовательные присвоения изменчивым переменным?

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

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

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

Пример:

typedef struct
{
   volatile unsigned reg0;
   volatile unsigned reg1;
} Module;

volatile Module* module = (volatile Module*)0xFF000000u;

// two word stores, or one double-word store?
module->reg0 = 1;
module->reg1 = 2;

(Я спрошу об этом поставщика компилятора отдельно, но мне любопытно, какова каноническая / общественная интерпретация стандарта.)

Ответов (5)

Решение

Поведение, volatile похоже, зависит от реализации, отчасти из-за любопытного предложения, в котором говорится: «То, что составляет доступ к объекту, который имеет тип с изменяемым типом, определяется реализацией».

В ISO C 99, раздел 5.1.2.3, также есть:

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

Таким образом, хотя предъявляются требования о том, что volatile объект должен обрабатываться в соответствии с абстрактной семантикой (т.е. не оптимизирован), любопытно, что сама абстрактная семантика позволяет исключить мертвый код и потоки данных, которые являются примерами оптимизаций!

Боюсь, что для того, чтобы знать, что volatile будет и что не будет делать, вам придется обратиться к документации вашего компилятора.

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

Семантические описания в этом международном стандарте описывают поведение абстрактной машины, в которой вопросы оптимизации не имеют значения.

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

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

Доступ к изменчивым объектам оценивается строго в соответствии с правилами абстрактной машины.

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

Компилятор, который объединял бы volatile записываемые объекты в один, был бы несоответствующим и просто сломался.

Его изменение изменит наблюдаемое поведение программы. Таким образом, компилятор не может этого сделать.

Компилятору не разрешается делать два таких присваивания за одну запись в память. Должно быть две независимых записи из ядра. Ответ от @Lundin дает соответствующие ссылки на стандарт C.

Однако имейте в виду, что кеш - если он есть - может вас обмануть. Ключевое слово volatile не подразумевает "некэшированную" память. Таким образом, помимо использования volatile, вам также необходимо убедиться, что адрес 0xFF000000 отображается как некэшированный. Если адрес отображается как кэшированный, аппаратное обеспечение кэша может превратить два назначения в одну запись в память. Другими словами, для кэшированной памяти две операции записи в память ядра могут закончиться как одна операция записи в интерфейсе системной памяти.

Стандарт C не зависит от каких-либо отношений между операциями с изменчивыми объектами и операциями на реальной машине. В то время как в большинстве реализаций указывается, что такая конструкция *(char volatile*)0x1234 = 0x56; генерирует хранилище байтов со значением 0x56 для аппаратного адреса 0x1234, реализация может на досуге выделить пространство, например, для 8192-байтового массива и указать, что *(char volatile*)0x1234 = 0x56; будет немедленно сохранять 0x56 для элемента 0x1234 из этот массив, ничего не делая с аппаратным адресом 0x1234. В качестве альтернативы реализация может включать в себя некоторый процесс, который периодически сохраняет все, что находится в 0x1234 этого массива, по адресу оборудования 0x56.

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