Безопасен ли `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 с тем, что настолько ужасно неэффективно, что затопляет любые предполагаемые преимущества, которые компиляторы могут получить, не поддерживая такую ​​семантику.

Другие вопросы по тегам