如何在编译时为结构体/类自定义模拟sizeof运算符并实现编译期常量大小替换?
自定义结构体/类的编译期大小,实现mmio参数常量替换
嘿Artur,我来帮你解决这个问题~
首先直接回答你的核心疑问:你当前的代码没法保证T::size在编译期被解析为常量,因为SizeHelper里的size只是const static成员,而非constexpr。虽然部分优化器可能会做常量折叠,但这不是C++标准强制要求的,没法确保所有场景下都能得到你想要的反汇编效果(直接替换为133、13这类数值)。
怎么修改才能实现编译期常量效果?
只需要把SizeHelper里的size改成constexpr static成员即可,这样编译器会强制在编译期确定它的值:
template<size_t SizeInBytes = 0> struct SizeHelper { static constexpr size_t size = SizeInBytes; }; class Test : public SizeHelper<133> { public: }; class Test2 : public SizeHelper<13> { public: };
然后你的ReadMemory模板函数里的constexpr auto size = T::size;就会在编译期完全解析为具体的数值,mmio的第二个参数也会被直接替换成常量,反汇编里就会看到你期望的:
; 调用ReadMemory<Test> mmio(..., 133); ; 调用ReadMemory<Test2> mmio(...,13);
更贴合你场景的替代方案
考虑到你是通过版本宏切换内存偏移,并且需要通过“最大偏移量+字段类型大小”计算结构体实际大小,其实可以不用继承SizeHelper,直接在类内部定义constexpr大小,更直观:
// 版本偏移定义 namespace offsets { namespace version12 { constexpr std::ptrdiff_t data = 0; constexpr std::ptrdiff_t more_data = sizeof(float); constexpr std::ptrdiff_t class_ptr = more_data + sizeof(int) + 32 * sizeof(uint32_t); // 假设uint_t是uint32_t } // 其他版本... } // 切换版本 #define version offsets::version12 struct ExampleClass { // 编译期计算实际需要读取的内存大小 static constexpr size_t size = version::class_ptr + sizeof(void*); float GetData() { return *(float*)((uintptr_t)this + version::data); } int GetMoreData() { return *(int*)((uintptr_t)this + version::more_data); } };
这种方式同样能让size成为编译期常量,调用ReadMemory<ExampleClass>时,mmio的参数会被直接替换为计算后的常量值,而且不需要额外的继承模板,代码可读性更好。
关键注意点
- 确保你的编译器支持C11或更高版本(
constexpr static成员是C11引入的); - 如果
size的计算依赖其他编译期常量(比如版本偏移),一定要把这些偏移也定义为constexpr,否则编译器无法在编译期完成计算; - 使用
constexpr的好处是,如果不小心引入了运行时依赖,编译器会直接报错,帮你提前发现问题,而不是依赖不确定的优化。
内容的提问来源于stack exchange,提问作者Artur K.
相关产品推荐
相关产品推荐

