如何获取结构体成员的地址/偏移以实现序列化?
问题描述
我正尝试实现一个既能设置结构体成员,又能将数据序列化输出到外部的函数。为实现序列化,我需要知晓结构体成员的大小和偏移,但在获取偏移时遇到了困难。
在以下代码中,直接打印member的值能得到正确结果,但无法将其转换为整数类型。另外,通过计算成员地址与结构体地址的差值可以得到偏移,但感觉这种方式过于复杂。
#include <stdint.h> #include <stddef.h> #include <stdio.h> struct Data { int32_t i; uint32_t u; bool b; char c; }; Data g_data; template <typename Type> void set(Type Data::*member, const Type& value) { // 打印正常工作 printf("Size: %lu\tAddress = %lu\n", sizeof(Type), member); // 强制转换报错 // printf("Size: %lu\tAddress = %lu\n", sizeof(Type), reinterpret_cast<uintptr_t>(member)); // 可行但过于复杂? // printf("Size: %lu\tAddress = %lu\n", sizeof(Type), reinterpret_cast<uintptr_t>(&(g_data.*member)) - reinterpret_cast<uintptr_t>(&g_data)); g_data.*member = value; } int main() { set(&Data::i, static_cast<int32_t>(1)); set(&Data::u, static_cast<uint32_t>(2U)); set(&Data::b, true); set(&Data::c, '1'); return 0; }
解决方案
为什么直接转换成员指针会报错
C++中,Type Data::*这类指向成员的指针不是普通内存地址,其底层表示可能包含额外信息(比如处理虚继承的逻辑),因此标准不允许用reinterpret_cast直接将其转换为uintptr_t,编译器会直接报错,这属于未定义行为范畴。
正确获取成员偏移的方式
想要可靠获取结构体成员的偏移量,有两种标准且简洁的方法:
- 基于空指针计算偏移
无需全局结构体实例,直接对空指针进行成员地址计算(仅计算偏移,不实际解引用空指针,符合标准要求):
uintptr_t offset = reinterpret_cast<uintptr_t>(&(reinterpret_cast<Data*>(nullptr)->*member));
- 封装通用偏移获取模板
可以写一个通用模板函数,适配任意结构体的成员指针:
template <typename Struct, typename Member> size_t get_member_offset(Member Struct::*member) { return reinterpret_cast<size_t>(&(reinterpret_cast<Struct*>(nullptr)->*member)); }
使用时直接调用get_member_offset(member)即可得到偏移量。
修改后的代码示例
#include <stdint.h> #include <stddef.h> #include <stdio.h> struct Data { int32_t i; uint32_t u; bool b; char c; }; Data g_data; template <typename Struct, typename Member> size_t get_member_offset(Member Struct::*member) { return reinterpret_cast<size_t>(&(reinterpret_cast<Struct*>(nullptr)->*member)); } template <typename Type> void set(Type Data::*member, const Type& value) { size_t offset = get_member_offset(member); printf("Size: %zu\tOffset = %zu\n", sizeof(Type), offset); g_data.*member = value; } int main() { set(&Data::i, static_cast<int32_t>(1)); set(&Data::u, static_cast<uint32_t>(2U)); set(&Data::b, true); set(&Data::c, '1'); return 0; }
注意事项
- 不要依赖直接打印成员指针的结果,这是编译器扩展行为,不同编译器输出可能不一致,标准未规定成员指针的打印格式。
- 如果使用
offsetof宏(来自<cstddef>),要求结构体是标准布局类型;而空指针计算方式无此限制,但需保证成员指针有效。
内容的提问来源于stack exchange,提问作者Armandas
相关产品推荐
相关产品推荐

