c++structundefined-behaviorderived-classbit-fields

Reserve memory in base class to be used in derived class c++


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?


Solution

  • 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.