英文:
How to get the address/offset of the struct member for serialization purposes
问题
I'm here to provide the translation for your code. Here's the translated code:
#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)
{
// Works for printing
printf("Size: %lu\tAddress = %lu\n", sizeof(Type), member);
// Invalid cast error
// printf("Size: %lu\tAddress = %lu\n", sizeof(Type), reinterpret_cast<uintptr_t>(member));
// Works, but unnecessarily complex?
// 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;
}
Please note that the code content is translated, but any code-related discussions or explanations are not included.
英文:
I am trying to implement a function that sets a struct member and also serializes the data to an external output. For serialization purposes, I need to know the size and offset of the struct member, but I am struggling to obtain the offset.
In the following code, when I print the value of member
, I get correct values, but I'm not able to convert it to integer.
#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)
{
// Works for printing
printf("Size: %lu\tAddress = %lu\n", sizeof(Type), member);
// Invalid cast error
// printf("Size: %lu\tAddress = %lu\n", sizeof(Type), reinterpret_cast<uintptr_t>(member));
// Works, but unnecesarily complex?
// 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;
}
答案1
得分: 2
获取类类型中数据成员的偏移量有两种选项:
offsetof
// in <cstddef>
#define offsetof(type, member) /* implementation-defined */
这个宏会扩展为类型为 std::size_t
的整数常量表达式,并给出从类型的开头开始的字节偏移量。
这正是你想要的。
但是,要使用它,set
需要接受偏移量而不是数据成员的指针:
template <typename T>
void set(std::size_t offset, const T& value)
{
std::printf("Size: %zu\tAddress = %zu\n", sizeof(T), offset);
// may require std::addressof to be safe against overloads of &
std::memcpy(reinterpret_cast<char*>(&g_data), &value, sizeof(value));
}
int main()
{
set(offsetof(Data, i), std::int32_t{1});
set(offsetof(Data, u), std::uint32_t{2});
set(offsetof(Data, b), true);
set(offsetof(Data, c), '1');
}
注意 1:std::size_t
的正确格式说明符是 %zu
,而不是 %lu
。
注意 2:offsetof
要求 Data
是标准布局类型,并且像这样写入其字节需要它是可平凡复制类型。对于像你的简单结构体来说,这是成立的。
std::bit_cast
std::bit_cast
是 C++20 的函数,它接受一个对象的字节,并将其转换为具有相同字节但不同类型的另一个对象。由于指向数据成员的指针通常只是底层的偏移量,所以这样可以工作:
template <typename T>
void set(Type Data::*member, const T& value)
{
std::printf("Size: %zu\tAddress = %zu\n",
sizeof(T),
std::bit_cast<std::size_t>(member));
g_data.*member = value;
}
int main()
{
set(&Data::i, std::int32_t{1});
set(&Data::u, std::uint32_t{2});
set(&Data::b, true);
set(&Data::c, '1');
}
然而,虽然这在技术上可以工作,而且更接近你的原始代码,不要这样做。它对实现做了太多假设,即:
- 指向数据成员的底层表示方式
- 指向数据成员的指针具有与
std::size_t
相同的大小
通过一些巧妙的元编程,你可以解决 2.,选择一个具有匹配大小的整数,但 1. 是不可解决的。
英文:
To obtain the offset of a data member in a class type, you have two options:
offsetof
// in <cstddef>
#define offsetof(type, member) /* implementation-defined */
This macro expands to an integral constant expression of type std::size_t
and gives us the offset in bytes from the start of the type.
This is exactly what you want.
However, to use it, set
would need to take the offset instead of the pointer to the data member:
template <typename T>
void set(std::size_t offset, const T& value)
{
std::printf("Size: %zu\tAddress = %zu\n", sizeof(T), offset);
// may require std::addressof to be safe against overloads of &
std::memcpy(reinterpret_cast<char*>(&g_data), &value, sizeof(value));
}
int main()
{
set(offsetof(Data, i), std::int32_t{1});
set(offsetof(Data, u), std::uint32_t{2});
set(offsetof(Data, b), true);
set(offsetof(Data, c), '1');
}
Note 1: the proper format specifier for std::size_t
is %zu
, not %lu
.
Note 2: offsetof
requires Data
to be a standard layout type, and writing to its bytes like this requires it to be a trivially copyable type. For a simple struct like yours, this is the case.
std::bit_cast
std::bit_cast
is a C++20 function which takes the bytes of one object, and converts it to another object with the same bytes, but different type. Since pointers to data members are typically just an offset under the hood, this would work:
template <typename T>
void set(Type Data::*member, const T& value)
{
std::printf("Size: %zu\tAddress = %zu\n",
sizeof(T),
std::bit_cast<std::size_t>(member));
g_data.*member = value;
}
int main()
{
set(&Data::i, std::int32_t{1});
set(&Data::u, std::uint32_t{2});
set(&Data::b, true);
set(&Data::c, '1');
}
However, while this technically works and is closer to your original code, do not do this. It makes too many assumptions about the implementation, namely:
- how pointers to data members are represented under the hood
- that pointers to data members have the same size as
std::size_t
With some clever metaprogramming you could solve 2. by choosing an integer with matching size, but 1. is not solvable.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论