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

85 views Asked by At

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?

2

There are 2 answers

1
Some programmer dude On BEST ANSWER

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.

0
Swift - Friday Pie On

You don't have polymorphic behaviour here, you're doing a semi-legal type-punning with wrong memory location.

First part of problem - object slicing: IRegister ireg = reg;

Second part of problem - FaultsStatusRegister doesn't initialize self, which includes padding and you didn't initializeanything but OTP_FLT, which invokes UB. While copying\slicing state of padded bits and bytes might not be copied and per as-if rule compiler might not "copy" fields which weren't initialized.

Third part of problem is result of the first - FaultsStatusRegister::asU16() atempts access outside of object. std::bit_cast<uint16_t*>(this) would be legal only for instance FaultsStatusRegister. IRegister might be covering only one byte. You're accessing something else in automatic storage memory.

Fourth, representation and layout of memory fields is not regulated by standard.

What you're trying to do with interface is legal to be done via recurrent template pattern.

What you're trying to do with bitfields should be done by union of structs with common starting sequence if memory representation must be portable.