Приведите энергозависимый массив к энергонезависимому массиву - PullRequest
14 голосов
/ 25 июня 2019

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

storeArray((unsigned char *) buffer);

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

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

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

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

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

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

EDIT: @Cacahuete Frito связал очень похожий вопрос: Безопасен ли `memcpy ((void *) dest, src, n)` с массивом `volatile`?

Ответы [ 4 ]

13 голосов
/ 25 июня 2019

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

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

Относительно второго вопроса: арифметика указателя в порядке, он просто преобразует buffer в unsigned char* и затем добавляет 3 к полученному указателю, указывая на buffer[3] (но с неверной квалификацией).

6 голосов
/ 25 июня 2019

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

Функция, записывающая что-то «на аппаратное обеспечение», вероятно, должна иметь параметр 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 голосов
/ 25 июня 2019
  1. Если массив изменяется в прерывании, вам необходимо предоставить механизм для доступа и изменения его атомарным способом. Если вы не выполняете какие-либо операции RW или RMW, они могут быть неудачными, а данные несовместимыми.

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

1 голос
/ 25 июня 2019

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

...