获取结构成员的地址/偏移量,用于序列化目的。

huangapple go评论69阅读模式
英文:

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.

Compiler Explorer

#include &lt;stdint.h&gt;
#include &lt;stddef.h&gt;
#include &lt;stdio.h&gt;

struct Data {
    int32_t i;
    uint32_t u;
    bool b;
    char c;
};

Data g_data;

template &lt;typename Type&gt;
void set(Type Data::*member, const Type&amp; value)
{
    // Works for printing
    printf(&quot;Size: %lu\tAddress = %lu\n&quot;, sizeof(Type), member);

    // Invalid cast error
    // printf(&quot;Size: %lu\tAddress = %lu\n&quot;, sizeof(Type), reinterpret_cast&lt;uintptr_t&gt;(member));

    // Works, but unnecesarily complex?
    // printf(&quot;Size: %lu\tAddress = %lu\n&quot;, sizeof(Type), reinterpret_cast&lt;uintptr_t&gt;(&amp;(g_data.*member)) - reinterpret_cast&lt;uintptr_t&gt;(&amp;g_data));

    g_data.*member = value;
}

int main() {
    set(&amp;Data::i, static_cast&lt;int32_t&gt;(1));
    set(&amp;Data::u, static_cast&lt;uint32_t&gt;(2U));
    set(&amp;Data::b, true);
    set(&amp;Data::c, &#39;1&#39;);

    return 0;
}

答案1

得分: 2

获取类类型中数据成员的偏移量有两种选项:

offsetof

// in &lt;cstddef&gt;
#define offsetof(type, member) /* implementation-defined */

这个宏会扩展为类型为 std::size_t 的整数常量表达式,并给出从类型的开头开始的字节偏移量。
这正是你想要的。

但是,要使用它,set 需要接受偏移量而不是数据成员的指针:

template &lt;typename T&gt;
void set(std::size_t offset, const T&amp; value)
{
    std::printf(&quot;Size: %zu\tAddress = %zu\n&quot;, sizeof(T), offset);
    // may require std::addressof to be safe against overloads of &amp;
    std::memcpy(reinterpret_cast&lt;char*&gt;(&amp;g_data), &amp;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), &#39;1&#39;);
}

注意 1:std::size_t 的正确格式说明符是 %zu,而不是 %lu

注意 2:offsetof 要求 Data 是标准布局类型,并且像这样写入其字节需要它是可平凡复制类型。对于像你的简单结构体来说,这是成立的。

std::bit_cast

std::bit_cast 是 C++20 的函数,它接受一个对象的字节,并将其转换为具有相同字节但不同类型的另一个对象。由于指向数据成员的指针通常只是底层的偏移量,所以这样可以工作:

template &lt;typename T&gt;
void set(Type Data::*member, const T&amp; value)
{
    std::printf(&quot;Size: %zu\tAddress = %zu\n&quot;,
                sizeof(T),
                std::bit_cast&lt;std::size_t&gt;(member));
    g_data.*member = value;
}

int main()
{
    set(&amp;Data::i, std::int32_t{1});
    set(&amp;Data::u, std::uint32_t{2});
    set(&amp;Data::b, true);
    set(&amp;Data::c, &#39;1&#39;);
}

然而,虽然这在技术上可以工作,而且更接近你的原始代码,不要这样做。它对实现做了太多假设,即:

  1. 指向数据成员的底层表示方式
  2. 指向数据成员的指针具有与 std::size_t 相同的大小

通过一些巧妙的元编程,你可以解决 2.,选择一个具有匹配大小的整数,但 1. 是不可解决的。

英文:

To obtain the offset of a data member in a class type, you have two options:

offsetof

// in &lt;cstddef&gt;
#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 &lt;typename T&gt;
void set(std::size_t offset, const T&amp; value)
{
    std::printf(&quot;Size: %zu\tAddress = %zu\n&quot;, sizeof(T), offset);
    // may require std::addressof to be safe against overloads of &amp;
    std::memcpy(reinterpret_cast&lt;char*&gt;(&amp;g_data), &amp;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), &#39;1&#39;);
}

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 &lt;typename T&gt;
void set(Type Data::*member, const T&amp; value)
{
    std::printf(&quot;Size: %zu\tAddress = %zu\n&quot;,
                sizeof(T),
                std::bit_cast&lt;std::size_t&gt;(member));
    g_data.*member = value;
}

int main()
{
    set(&amp;Data::i, std::int32_t{1});
    set(&amp;Data::u, std::uint32_t{2});
    set(&amp;Data::b, true);
    set(&amp;Data::c, &#39;1&#39;);
}

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:

  1. how pointers to data members are represented under the hood
  2. 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.

huangapple
  • 本文由 发表于 2023年6月12日 09:20:35
  • 转载请务必保留本文链接:https://go.coder-hub.com/76453158.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定