I am writing some low level abstractions for communicating with some chip via SPI and I have created registers abstraction to avoid tricky bit manipulation. I thought that i may create interface containing method that converts register struct into uint16_t and it works fine when I call that method from instance of a register struct but when i call it as interface method i am getting undefined behaviour - i suspect it is because interface/abstract doesn't reserve memory for the actual fields.
#include <cstdio>
#include <cstdint>
// interface struct
struct IRegister {
[[nodiscard]] constexpr uint16_t asU16() {
return *std::bit_cast<uint16_t*>(this);
}
};
//Register struct - i have like 20 of those, thats why i used interface
struct FaultsStatusRegister : IRegister {
uint16_t CS_OCP_FLT_PHASE_A : 1;
uint16_t CS_OCP_FLT_PHASE_B : 1;
uint16_t CS_OCP_FLT_PHASE_C : 1;
uint16_t CP_FLT : 1;
uint16_t DVDD_OCP_FLT : 1;
uint16_t DVDD_UV_FLT : 1;
uint16_t DVDD_OV_FLT : 1;
uint16_t BK_OCP_FLT : 1;
uint16_t OTS_FLT : 1;
uint16_t OTW_FLT : 1;
uint16_t LOCK_FLT : 1;
uint16_t WD_FLT : 1;
uint16_t OTP_FLT : 1;
uint16_t Reserved : 3;
};
int main()
{
FaultsStatusRegister reg;
reg.CS_OCP_FLT_PHASE_C = 1;
reg.CS_OCP_FLT_PHASE_A = 1;
reg.CS_OCP_FLT_PHASE_B = 1;
reg.OTP_FLT = 1;
printf("%b \n", reg.asU16()); //This if fine: 1000000000111
IRegister ireg = reg;
printf("%b \n", ireg.asU16()); // UB? : 11100000000
return 0;
}
How can i fix this? Or somehow can i prevent usage of the IRegister that causes bad behaviour? I don't really need to use polimorphism, if i can't fix polimorifc behaviour than i would like to somehow block it, best in compile time. Is that possible?
The problem is that the variable definition
does not initialize any of the members. All fields will have indeterminate values. And using an indeterminate value in any way leads to undefined behavior.
You need e.g.
to zero-initialize all the members.
Furthermore, in the
IRegister::asU16function,thisis theIRegisterpart of the object. There's no way to get access to any possible child-class members.And as mentioned,
will slice the
regobject.On another note, the order of bits that share a "word" in a bit-field is implementation specified. It can be different in one compiler from the next. Not to mention the endianness issue.