提问人:lukasz luko 提问时间:10/22/2023 更新时间:10/23/2023 访问量:78
在基类中保留要在派生类 c++ 中使用的内存
Reserve memory in base class to be used in derived class c++
问:
我正在编写一些低级抽象,用于通过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 行为,那么我想以某种方式阻止它,最好在编译时。 这可能吗?
答:
问题在于变量定义
FaultsStatusRegister reg;
不初始化任何成员。所有字段都将具有不确定值。以任何方式使用不确定值都会导致未定义的行为。
例如,您需要
FaultsStatusRegister reg{};
对所有成员进行零初始化。
此外,在函数中,是对象的 IRegister
部分。无法访问任何可能的子类成员。IRegister::asU16
this
如前所述,
IRegister ireg = reg;
将对对象进行切片。reg
另一方面,在位字段中共享“字”的位的顺序是指定的。在一个编译器中,它可能与下一个编译器不同。更不用说字节序问题了。
评论
你在这里没有多态行为,你正在做一个半合法的类型双关语,内存位置错误。
问题的第一部分 - 对象切片:IRegister ireg = reg;
问题的第二部分 - 没有初始化 self,其中包括填充,并且您没有初始化任何东西,但 OTP_FLT 调用 UB。虽然可能不会复制填充位和字节的复制\切片状态,并且根据按条件规则编译器可能不会“复制”未初始化的字段。FaultsStatusRegister
问题的第三部分是第一部分的结果 - 尝试访问对象之外。 例如,只有合法。 可能只覆盖一个字节。您正在访问自动存储内存中的其他内容。FaultsStatusRegister::asU16()
std::bit_cast<uint16_t*>(this)
FaultsStatusRegister
IRegister
第四,内存场的表示和布局不受标准约束。
您尝试使用接口执行的操作是合法的,可以通过循环模板模式完成。
如果内存表示必须是可移植的,则应通过将 s 与公共起始序列并集来尝试使用位字段来完成。struct
评论
IRegister ireg = reg;
您正在对对象进行切片。您需要使用引用或指针来实现动态多态性。我不明白“我真的不需要多态性”的部分。如果不需要多态性,则不应使用接口类。如果你需要任何类型的接口,你总是需要多态性,无论是编译时多态性(模板)还是运行时多态性(虚函数)都取决于你的需要。bit_cast<uint16_t>(reg);
void spi_write_reg(ControlTable address, IRegister value); IRegister spi_read_reg(ControlTable address);