使用'volatile'数组的'memcpy((void*)dest,src,n)`安全吗?

使用'volatile'数组的'memcpy((void*)dest,src,n)`安全吗?,c,casting,interrupt,volatile,memcpy,C,Casting,Interrupt,Volatile,Memcpy,我有一个用于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 Eu

我有一个用于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;
下面是填充缓冲区的函数(将使用中断发送):

在使用
memcpy()
时,我知道没有其他人会使用缓冲区(原子),因为
while
循环确保最后一条消息已发送,因此中断被禁用

以这种方式丢弃
volatile
是否安全,这样我就可以使用
memcpy()
或者我应该创建一个名为
memcpy\u 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;
}
编辑2(添加上下文):

void    eusart_end_transmission (void)
{

    reg_PIE1_TXIE_write(false); /* TXIE is TX interrupt enable */
    eusart.tx.msg_len   = 0;
    eusart.tx.msg_posn  = 0;
}

void    eusart_tx_send_next_c   (void)
{
    uint16_t    tmp;

    if (data_9b) {
        tmp     = eusart.tx.buff.b9[eusart.tx.msg_posn++];
        reg_TXSTA_TX9D_write(tmp >> 8);
        TXREG   = tmp;
    } else {
        TXREG   = eusart.tx.buff.b8[eusart.tx.msg_posn++];
    }
}

void __interrupt()  isr(void)
{

    if (reg_PIR1_TXIF_read()) {
        if (eusart.tx.msg_posn >= eusart.tx.msg_len)
            eusart_end_transmission();
        else
            eusart_tx_send_next_c();
    }
}
虽然
volatile
可能不需要is(我在另一个问题中问了它:),但这个问题仍然应该在假设需要
volatile
的情况下得到回答,以便真正需要
volatile
的未来用户(例如,我在实现RX缓冲时)可以知道该怎么做


编辑(相关)(7月19日):

基本上说,
volatile
是不需要的,因此这个问题消失了

memcpy((void*)dest,src,n)
volatile
数组是否安全

不可以。在一般情况下,
memcpy()
未指定与易失性存储器一起正常工作。
OP的案例看起来可以摆脱波动性,但发布的代码不足以确定

如果代码想要
memcpy()
volatile
内存,请编写helper函数

OP的代码在错误的位置有
限制
。暗示

volatile void *memcpy_v(volatile void *restrict dest,
            const volatile void *restrict src, size_t n) {
    const volatile unsigned char *src_c = src;
    volatile unsigned char *dest_c      = dest;

    while (n > 0) {
        n--;
        dest_c[n] = src_c[n];
    }
    return  dest;
}
编写自己的
memcpy_v()
的一个独特原因是编译器可以“理解”/“分析
memcpy()
,并发出与预期非常不同的代码,甚至可以优化它,如果编译器认为不需要副本的话。提醒自己,编译器认为
memcpy()
操纵内存是非易失性的


然而OP使用的是
volatile结构Eusart Eusart错误。访问
eusart
需要
volatile
不提供的保护

在OP的情况下,代码可以将
volatile
放在缓冲区上,然后使用
memcpy()
就可以了

剩下的一个问题是OP如何使用
eusart
的代码很少。使用
volatile
并不能解决OP的问题。OP确实断言“我以原子方式向它写入”,但没有发布
atomic
code,这是不确定的


类似以下代码的好处是eusart.tx.msg_lenvolatile,但这还不够
volatile
确保
.tx.msg\u len
不会被缓存,而是每次重新读取

while (eusart.tx.msg_len)
    ;
然而,
.tx.msg_len
的读取未指定为原子。当
.tx.msg_len==256
并触发ISR时,LSbyte(256中的0)和MSbyte(255中的0)的读取量将递减,非ISR代码可能会将
.tx.msg_len
视为0,而不是255或256,从而在错误的时间结束循环。需要将
.tx.msg_len
的访问指定为不可分割(原子),否则代码偶尔会神秘地失败

while(eusart.tx.msg_len)也是一个无端循环。如果变速箱因空以外的原因停止,则while环路永远不会退出

相反,建议在检查或更改
eusart.tx.msg\u len、eusart.tx.msg\u posn时阻止中断。查看编译器对
原子的支持

size_t tx_msg_len(void) {
  // pseudo-code
  interrupt_enable_state = get_state();
  disable_interrupts();
  size_t len = eusart.tx.msg_len;
  restore_state(interrupt_enable_state);
  return len;
}

通用通信代码理念:

  • 当非ISR代码读取或写入
    eusart
    时,确保ISR永远不能更改
    eusart

  • 不要在步骤1中长时间阻塞
    ISR

  • 不要假设底层的
    ISR()
    将成功链接输入/输出,而不会出现故障。顶层代码应该准备好在输出停止时重新启动输出


  • 该标准缺少程序员可以要求在执行特定的
    volatile
    指针访问之前完成通过普通指针访问存储区域的操作的任何方法,并且还缺乏任何方法来确保通过普通指针访问存储区域的操作在执行某个特定的
    volatile
    指针访问之后才能执行。由于
    volatile
    操作的语义是由实现定义的,因此标准的作者可能期望编译器编写者能够识别其客户何时可能需要此类语义,并以符合这些需求的方式指定其行为。不幸的是,这并没有发生


    实现所需的语义需要使用“流行的扩展”,例如编译器特有的内部模式clang的
    -fms volatile
    模式,或者用效率极低的东西取代
    memcpy
    ,从而淹没编译器不支持这种语义可能获得的任何假定优势。

    您的平台是否规定
    volatile
    使对象线程安全?因为在大多数平台上,这是不正确的。它是线程安全的,不是因为
    volatile
    ,而是因为只有一个线程,而且在我开始写之前,中断被检查为禁用,然后被启用。因此,有人在同一时间胡闹的可能性为0。您甚至需要
    volatile
    做什么?因为该变量用于普通代码和中断中。只是在写它的那一刻,我证明没有其他人在使用它,但在其他任何时刻,变量在主循环和中断之间共享。我的理解是,严格地说,如果你访问一个小于c的变量
    while (eusart.tx.msg_len)
        ;
    
    size_t tx_msg_len(void) {
      // pseudo-code
      interrupt_enable_state = get_state();
      disable_interrupts();
      size_t len = eusart.tx.msg_len;
      restore_state(interrupt_enable_state);
      return len;
    }