在基类中保留要在派生类 c++ 中使用的内存

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

提问人:lukasz luko 提问时间:10/22/2023 更新时间:10/23/2023 访问量:78

问:

我正在编写一些低级抽象,用于通过SPI与某些芯片进行通信,并且我创建了寄存器抽象以避免棘手的位操作。我以为我可以创建包含将寄存器结构转换为uint16_t的方法的接口,当我从寄存器结构的实例调用该方法时,它可以正常工作,但是当我将其称为接口方法时,我得到了未定义的行为 - 我怀疑这是因为接口/抽象没有为实际字段保留内存。

#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;
}

我该如何解决这个问题?或者我可以以某种方式阻止使用导致不良行为的 IRegister 吗?我真的不需要使用 polimorphism,如果我无法修复 polimorifc 行为,那么我想以某种方式阻止它,最好在编译时。 这可能吗?

C++ 结构 undefined-behavior 派生类 位字段

评论

4赞 Weijun Zhou 10/22/2023
IRegister ireg = reg;您正在对对象进行切片。您需要使用引用或指针来实现动态多态性。我不明白“我真的不需要多态性”的部分。如果不需要多态性,则不应使用接口类。如果你需要任何类型的接口,你总是需要多态性,无论是编译时多态性(模板)还是运行时多态性(虚函数)都取决于你的需要。
1赞 Red.Wave 10/22/2023
为什么不只是 ?如此复杂的设计有什么意义?此外,为了安全起见,您最好为所有这些字段提供默认值;无需构造函数声明。bit_cast<uint16_t>(reg);
1赞 n. m. could be an AI 10/22/2023
阻止不需要的切片的最简单方法是使 IRegister 的构造函数受到保护。
0赞 lukasz luko 10/22/2023
@Red.Wave,我可能会坚持你的方法。我想也许我能够制作出类似的东西,在引擎盖下称为 asU16,但我想我会坚持使用古老的uint16_t,我会bit_casting每个寄存器void spi_write_reg(ControlTable address, IRegister value); IRegister spi_read_reg(ControlTable address);
0赞 Red.Wave 10/22/2023
不幸的是,微 API 并不是最好的做法。从 C 向 C++ 的转变最近才开始。我不会拘泥于这些惯例。有了足够的硬件知识,甚至可以为特定的MCU系列开发更好的C++ API。

答:

4赞 Some programmer dude 10/22/2023 #1

问题在于变量定义

FaultsStatusRegister reg;

不初始化任何成员。所有字段都将具有不确定值。以任何方式使用不确定值都会导致未定义的行为

例如,您需要

FaultsStatusRegister reg{};

对所有成员进行零初始化。

此外,在函数中,是对象的 IRegister 部分。无法访问任何可能的子类成员。IRegister::asU16this

如前所述,

IRegister ireg = reg;

将对对象进行切片reg


另一方面,在位字段中共享“字”的位的顺序是指定的。在一个编译器中,它可能与下一个编译器不同。更不用说字节序问题了。

评论

0赞 lukasz luko 10/22/2023
我知道初始化,我假设值为 0,因为我在在线环境中测试它,在大多数情况下,该环境将变量放在填充 0 的内存中。就我而言,这并不重要。我还可以假设字节序,因为我知道编译器和平台。我忘记了切片。如果没有办法以某种方式使基类的大小与我的示例中的派生类相同,我可能会坚持上面建议的 std::bit_cast<uint16_t>(reg)。
1赞 Swift - Friday Pie 10/23/2023 #2

你在这里没有多态行为,你正在做一个半合法的类型双关语,内存位置错误。

问题的第一部分 - 对象切片:IRegister ireg = reg;

问题的第二部分 - 没有初始化 self,其中包括填充,并且您没有初始化任何东西,但 OTP_FLT 调用 UB。虽然可能不会复制填充位和字节的复制\切片状态,并且根据按条件规则编译器可能不会“复制”未初始化的字段。FaultsStatusRegister

问题的第三部分是第一部分的结果 - 尝试访问对象之外。 例如,只有合法。 可能只覆盖一个字节。您正在访问自动存储内存中的其他内容。FaultsStatusRegister::asU16()std::bit_cast<uint16_t*>(this)FaultsStatusRegisterIRegister

第四,内存场的表示和布局不受标准约束。

您尝试使用接口执行的操作是合法的,可以通过循环模板模式完成。

如果内存表示必须是可移植的,则应通过将 s 与公共起始序列并集来尝试使用位字段来完成。struct