Reading a struct from a read only memory

The most practical approach would be to lose the const. By a strict reading of the standard, gCalibrationData shouldn't be allowed to be const, since writing to a const object -- regardless of who does it -- leads to undefined behavior.

Failing that, though, just define it as extern const (and, if necessary to placate the linker, put the non-extern definition in its own translation unit. That will get you your const-correctness checking, allow the compiler to, e.g., do hoisting optimizations based on the initial values of the calibration data, while still preventing it from making any specific assumptions about those values at compile time.


One solution could be to declare a buffer in a separate source file, to inform the linker of size of data_block and then define gCalibrationData to be a symbol whose value is the begining of this buffer:

data_block.cpp:

//no initialization performed here, just used to
//transmit to the linker the information of the size
//and alignment of data_block
extern "C"{//simpler name mangling
[[gnu::section(".caldata")]] volatile
aligned_storage<sizeof(data_block),alignof(data_block)> datablock_buffer;
}

//then we specify that gCalibrationData refers to this buffer
extern const volatile data_block
gCalibrationData [[gnu::alias("datablock_buffer")]];

Alternatively the definition of gCalibrationData symbol can be done via a linker script:

SECTIONS{
  .caldata : {
    gCalibrationData = . ;
    data_block.o(.caldata)
    }
  }

gCalibrationData is an alias to an data_block_buffer. This will not cause undefined behavior because such aliasing is permitted by the language: data_block_buffer provides storage for gCalibrationData.

Semanticaly, the extern specifier is used to say that this declaration is not a definition of the value of gCalibrationData. Nevertheless the alias attribute is a definition of the symbol for the linker.

data_block.hpp

extern const volatile data_block gCalibrationData;

//and copy must be enabled for volatile:
struct data_block{
  /*...*/
  data_block(const data_block&) =default; 

  data_block& operator=(const data_block&) =default;

  data_block(const volatile data_block& other){
    //the const cast means: you are responsible not to 
    //perform this operation while performing a rom update.
    memcpy(this,const_cast<const data_block*>(&other);
    }

  data_block& operator=(const volatile data_block& other){
    memmove(this,const_cast<const data_block*>(&other);
    //or memcpy if you are sure a self assignment will never happen.
    return *this;
    }
  };