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
FaultsStatusRegister reg;
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.
FaultsStatusRegister reg{};
to zero-initialize all the members.
Furthermore, in the IRegister::asU16
function, this
is the IRegister
part of the object. There's no way to get access to any possible child-class members.
And as mentioned,
IRegister ireg = reg;
will slice the reg
object.
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.