Приведите энергозависимый массив к энергонезависимому массиву

У меня есть глобальный volatile массив без знака volatile unsigned char buffer[10], в который данные записываются в прерывании. У меня есть функция, которая принимает неподписанный символ * и сохраняет это значение в аппаратном обеспечении (EEPROM) void storeArray(unsigned char *array), в этом примере первые три значения. Безопасно ли преобразовывать энергозависимый массив в энергонезависимый массив следующим образом?

store_array((unsigned char *) buffer);

Я прочитал следующее, что мне не совсем понятно, но которое касается меня:

6.7.3: 5 Если предпринята попытка обратиться к объекту, определенному с типом, определенным с помощью volatile, с использованием lvalue с типом, не определенным с помощью volatile, поведение не определено.

Влияет ли это на мой код?

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

store_array((unsigned char *) buffer + 3);

Если да, как повлияет приведение, если 3 добавлен в массив? BR и спасибо!

ОБНОВЛЕНИЕ: @Cacahuete Frito связал очень похожий вопрос: Безопасен ли 'memcpy ((void *) dest, src, n)' с массивом 'volatile'?

Ответы

Ответ 1

Да, стандартная цитата, которую вы разместили, охватывает именно то, что вы пытаетесь сделать. Выполняя приведение, вы притворяетесь, что объекты в массиве - это unsigned char когда они на самом деле являются volatile unsigned char, поэтому внутри функции вы обращаетесь к volatile объекту через lvalue без квалификатора volatile. Неопределенное поведение.

Ответ 2

Вы нашли правильный раздел стандарта, этот код ведет к неопределенному поведению.

Функция что - то пишет "к аппаратному", вероятно, следует иметь volatile параметр -qualifier, в зависимости от того, что "железо" есть. Если это регистр с отображением в памяти, буфер DMA или энергонезависимая память, то этот параметр определенно должен был быть volatile unsigned char* (или, необязательно, volatile uint8_t* который также следует рассматривать как символьный тип).


Подробности: C позволяет нам перебирать любой фрагмент данных, используя символьный указатель, C17 6.3.2.3/7:

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

Часть, которую вы цитируете о доступе к lvalue, относится к доступу к данным через тип указателя, отличный от того, который фактически хранится в этом месте. Проще говоря: независимо от того, сколько вы используете различных указателей, указывающих на него, фактические данные сохраняют свой первоначальный тип.

Доступ к данным через неверный тип указателя обычно даже не разрешен, но опять же символьный доступ является специальным исключением из "строгого правила псевдонимов", C17 6.5/7:

Объект должен иметь свое сохраненное значение, доступное только через выражение lvalue, которое имеет один из следующих типов:
...
- тип персонажа.

Таким образом, вы можете получить доступ к любому виду данных через символьный указатель, но если этот указатель не определен как volatile, вы вызываете неопределенное поведение в соответствии с процитированной вами частью, C17 6.7.3/5.

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


Что касается вашего последующего вопроса, приведение и buffer + 3 ничего не изменят: вы все еще работаете с символьным указателем без volatile квалификатора - того же типа. Фактические данные остаются типа volatile unsigned char, поэтому вы не можете получить к ним доступ из функции через unsigned char*.

Ответ 3

  1. Если массив изменяется в прерывании, вам необходимо предоставить механизм доступа и изменения его атомарным способом. Если вы не выполняете какие-либо операции RW или RMW, они могут быть неудачными, а данные несовместимыми.

  2. Доступ к изменяемым данным делает параметры f = unction также изменчивыми. storeArray(volatile unsigned char *) и никакое приведение не потребуется. Приведение только удаляет предупреждение. Даже если вы передадите ему энергонезависимые данные, они также будут работать.

Ответ 4

Как вы обнаружили, вы полагаетесь на "неопределенное поведение". Однако, в зависимости, помимо прочего, от разделения модулей компиляции (и таких вещей, как "оптимизация всей программы" (WPO)), это, вероятно, будет работать. В большинстве случаев компилятор (по крайней мере, gcc) недостаточно "умен", чтобы оптимизировать доступ к массиву между функциями в разных единицах компиляции. Тем не менее, чистый, безопасный и переносимый способ состоит в том, чтобы скопировать массив, делая зависимость значений энергонезависимого массива от энергозависимых, видимых для компилятора.