Безопасен ли `memcpy((void *)dest, src, n)` с массивом `volatile`?
У меня есть буфер, который я использую для UART, который объявлен так:
union Eusart_Buff {
uint8_t b8[16];
uint16_t b9[16];
};
struct Eusart_Msg {
uint8_t msg_posn;
uint8_t msg_len;
union Eusart_Buff buff;
};
struct Eusart {
struct Eusart_Msg tx;
struct Eusart_Msg rx;
};
extern volatile struct Eusart eusart;
А вот функция, которая заполняет буфер (который будет отправлен с использованием прерываний):
void eusart_msg_transmit (uint8_t n, void *msg)
{
if (!n)
return;
while (eusart.tx.msg_len)
;
if (data_9b) {
memcpy((void *)eusart.tx.buff.b9, msg,
sizeof(eusart.tx.buff.b9[0]) * n);
} else {
memcpy((void *)eusart.tx.buff.b8, msg,
sizeof(eusart.tx.buff.b8[0]) * n);
}
eusart.tx.msg_len = n;
eusart.tx.msg_posn = 0;
reg_PIE1_TXIE_write(true);
}
В момент использования memcpy()
Я знаю, что никто другой не собирается использовать буфер (атомарный), потому что while
Цикл гарантирует, что последнее сообщение было отправлено, и, следовательно, прерывание отключено.
Безопасно ли выбрасывать volatile
таким образом, чтобы я мог использовать memcpy()
или я должен сделать функцию, возможно, называется memcpy_v()
как эти, чтобы быть в безопасности?
void *memcpy_vin(void *dest, const volatile void *src, size_t n)
{
const volatile char *src_c = (const volatile char *)src;
char *dest_c = (char *)dest;
for (size_t i = 0; i < n; i++)
dest_c[i] = src_c[i];
return dest;
}
volatile void *memcpy_vout(volatile void *dest, const void *src, size_t n)
{
const char *src_c = (const char *)src;
volatile char *dest_c = (volatile char *)dest;
for (size_t i = 0; i < n; i++)
dest_c[i] = src_c[i];
return dest;
}
volatile void *memcpy_v(volatile void *dest, const volatile void *src, size_t n)
{
const volatile char *src_c = (const volatile char *)src;
volatile char *dest_c = (volatile char *)dest;
for (size_t i = 0; i < n; i++)
dest_c[i] = src_c[i];
return dest;
}
Редактировать:
Если мне нужны эти новые функции, учитывая, что я знаю, что никто не собирается изменять массив одновременно, имеет ли смысл использовать restrict
(возможно) помочь компилятору оптимизировать (если это возможно)? Возможно, так (поправьте меня, если я ошибаюсь):
volatile void *memcpy_v(restrict volatile void *dest,
const restrict volatile void *src,
size_t n)
{
const restrict volatile char *src_c = (const restrict volatile char *)src;
restrict volatile char *dest_c = (restrict volatile char *)dest;
for (size_t i = 0; i < n; i++)
dest_c[i] = src_c[i];
return dest;
}
3 ответа
Является
memcpy((void *)dest, src, n)
сvolatile
массив безопасен?
Нет. memcpy()
не указан для корректной работы с энергозависимой памятью.
И все же OP использует volatile struct Eusart eusart;
неправильно. Доступ к eusart
нуждается в защите, которая volatile
не обеспечивает.
В случае OP код может быть удален volatile
а затем использовать memcpy()
в ISR()
просто хорошо.
Остается проблема в непоказанном коде использования OP eusart
в неISR()
код. С помощью volatile
не решает проблему ОП там. ОП утверждает, что "я пишу в него атомарно", но без размещенного кода, это не точно.
Стандарт C17 говорит об этом в разделе 6.7.3 «Определители типов»:
Если предпринимается попытка сослаться на объект, определенный с типом с уточнением volatile, посредством использования lvalue с типом с уточнением volatile, поведение не определено.135)
135) Это относится к тем объектам, которые ведут себя так, как если бы они были определены с уточненными типами, даже если они никогда не были фактически определены как объекты в программе (например, объект по адресу ввода/вывода, отображаемому в памяти).
Так что нет, это не безопасно.
В стандарте отсутствуют какие-либо средства, с помощью которых программисты могут требовать, чтобы операции, которые обращаются к области памяти с помощью обычного указателя, выполнялись до того, как volatile
осуществляется доступ к указателю, а также отсутствуют какие-либо средства, гарантирующие, что операции, которые обращаются к области памяти с помощью обычного указателя, не выполняются до тех пор, пока не произойдет некоторое volatile
указатель доступа осуществляется. С семантикой volatile
Операции определяются реализацией, и авторы стандарта, возможно, ожидали, что разработчики компиляторов распознают, когда их клиентам может понадобиться такая семантика, и определят свое поведение в соответствии с этими потребностями. К сожалению, этого не произошло.
Достижение требуемой семантики будет либо использовать "популярное расширение", такое как -fms-volatile
режим clang, встроенная в компилятор или заменяющая memcpy
с тем, что настолько ужасно неэффективно, что затопляет любые предполагаемые преимущества, которые компиляторы могут получить, не поддерживая такую семантику.