Is `memcpy((void *)dest, src, n)` with a `volatile` array safe?

Is memcpy((void *)dest, src, n) with a volatile array safe?

No. In the general case, memcpy() is not specified to work correctly with volatile memory.
OP's case looks OK to cast away volatile, yet posted code is insufficient to be certain.

If code wants to memcpy() volatile memory, write the helper function.

OP's code has restrict in the wrong place. Suggest

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;
}

A singular reason for writing your own memcpy_v() is the a compiler can "understand"/analyze memcpy() and emit code that is very different than expected - even optimize it out, if the compiler thinks the copy is not needed. Remind oneself that the compiler thinks memcpy() manipulated memory is non-volatile.


Yet OP is using volatile struct Eusart eusart; incorrectly. Access to eusart needs protection that volatile does not provide.

In OP's case, code can drop volatile on the buffers and then use memcpy() just fine.

A remaining issue is in the scant code of how OP is using eusart. Using volatile does not solve OP's problem there. OP's does assert "I write to it atomically,", yet without posted atomic code, that is not certain.


Code like the below benefits with eusart.tx.msg_len being volatile, yet that is not sufficient. volatile insures .tx.msg_len is not cached and instead re-reads each time.

while (eusart.tx.msg_len)
    ;

Yet the read of .tx.msg_len is not specified as atomic. When .tx.msg_len == 256 and a ISR fires, decrementing .tx.msg_len, the read of the the LSbyte (0 from 256) and MSbyte (0 from 255), the non-ISR code may see .tx.msg_len as 0, not 255 nor 256, thus ending the loop at the wrong time. The access of .tx.msg_len needs to be specified as indivisible (atomic), else, once in a while code fails mysteriously.

while (eusart.tx.msg_len); also suffers from being an end-less loop. Should the transmission stop for some reason other than empty, the while loop never exits.

Recommend instead to block interrupts while inspecting or changing eusart.tx.msg_len, eusart.tx.msg_posn. Review your compilers support of atomic or

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;
}

General communication code ideas:

  1. While non-ISR code reads or writes eusart, make sure the ISR cannot ever change eusart.

  2. Do not block ISR for long in step #1.

  3. Do not assume underlying ISR() will chain input/output successfully without a hiccup. Top level code should be prepared to re-start output if it gets stalled.