使用 C++ template 构建一个通信用序列化反序列化模块
1. 序列化 PacketSerializer
分为以下几个步骤:
- 需要一个 buffer 来存储序列化过程中的数据,使用 std::vector
作为底层存储。 - 提供一个模板实现的基础函数 writeToBuffer,将基本类型数据写入 buffer。
- 提供一个模板函数 pack,支持将基础类型数据,以及其数组,以及 vector 类型进行序列化。
- 在 pack 函数的基础上,提供一个模板函数 packMultiple,支持将多个数据打包进行序列化。
- 提供接口函数 finalize,添加包头、包尾,组装成完整的协议,返回序列化后的数据 buffer。
1.1. writeToBuffer
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
template <typename T>
void writeToBuffer(const T& value) {
static_assert(std::is_integral_v<T> || std::is_floating_point_v<T>, "Type must be arithmetic");
const uint8_t* bytes = reinterpret_cast<const uint8_t*>(&value);
for (size_t i = 0; i < sizeof(T); i++) {
buffer_.push_back(bytes[i]);
}
}
template <typename T>
void writeArrayToBuffer(const T* array, size_t count) {
static_assert(std::is_integral_v<T> || std::is_floating_point_v<T>, "Array element type must be arithmetic");
for (size_t i = 0; i < count; i++) {
writeToBuffer(array[i]);
}
}
1.2. pack
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
template <typename T>
typename std::enable_if_t<std::is_integral_v<T> || std::is_floating_point_v<T>, PacketSerializer&> //
pack(const T& value) {
writeToBuffer(value);
return *this;
}
template <typename T, size_t N>
typename std::enable_if_t<std::is_integral_v<T> || std::is_floating_point_v<T>, PacketSerializer&> //
pack(const T (&array)[N]) {
writeArrayToBuffer(array, N);
return *this;
}
template <typename T>
typename std::enable_if_t<std::is_integral_v<T> || std::is_floating_point_v<T>, PacketSerializer&> //
pack(const std::vector<T>& vec) {
if (!vec.empty()) {
writeArrayToBuffer(vec.data(), vec.size());
}
return *this;
}
1.3. packMultiple
1
2
3
4
5
6
7
8
template <typename T, typename... Args>
PacketSerializer& packMultiple(const T& first, const Args&... args) {
pack(first);
if constexpr (sizeof...(args) > 0) {
packMultiple(args...);
}
return *this;
}
1.4. 打包原始结构体数据
提供另外一种类型打包接口,用于直接序列化 1 字节对齐的结构体数据。
1
2
3
4
5
6
template <typename T>
PacketSerializer& packRawData(const T& structData) {
static_assert(std::is_trivially_copyable_v<T>, "Type must be trivially copyable for direct packing");
const uint8_t* bytes = reinterpret_cast<const uint8_t*>(&structData);
return appendBytes(bytes, sizeof(T));
}
1.5. finalize
组装成完成的数据包,并返回序列化后的 packet。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
std::vector<uint8_t> finalize() {
std::vector<uint8_t> packet;
uint16_t total_length = 2 + 2 + static_cast<uint16_t>(buffer_.size()) + 1;
packet.push_back(static_cast<uint8_t>(frame_header_ & 0xFF));
packet.push_back(static_cast<uint8_t>((frame_header_ >> 8) & 0xFF));
packet.push_back(static_cast<uint8_t>(total_length & 0xFF));
packet.push_back(static_cast<uint8_t>((total_length >> 8) & 0xFF));
packet.insert(packet.end(), buffer_.begin(), buffer_.end());
const uint8_t checksum = calculateChecksum(packet.data(), packet.size());
packet.push_back(checksum);
return packet;
}
1.6. 便利函数
1
2
3
4
5
6
7
8
template <typename... Args>
std::vector<uint8_t> createPacket(uint16_t frame_header, const Args&... args) {
PacketSerializer serializer(frame_header);
if constexpr (sizeof...(args) > 0) {
serializer.packMultiple(args...);
}
return serializer.finalize();
}
1
2
3
4
5
6
7
template <typename T>
std::vector<uint8_t> createRawDataPacket(uint16_t frame_header, const T& structData) {
static_assert(std::is_trivially_copyable_v<T>, "Type must be trivially copyable for direct packing");
PacketSerializer serializer(frame_header);
serializer.packRawData(structData);
return serializer.finalize();
}
2. 反序列化 PacketDeserializer
首先解出header。payload部分的解析,与序列化提供相同步骤的接口函数 unpack,unpackRawData。并提供便利接口:
1
2
3
4
5
6
7
8
9
10
11
12
template <typename T, typename... Args>
bool unpackMultiple(PacketDeserializer& deserializer, T& first, Args&... args) {
if (deserializer.unpack(first) == 0) {
return false;
}
if constexpr (sizeof...(args) > 0) {
return unpackMultiple(deserializer, args...);
}
return true;
}
实际应用中,由于存在一些运行时判断,实际在使用 PacketDeserializer 的时候,需要依据具体的协议格式,进行相应的逻辑处理,即针对这些特殊协议实现一个特化版本的解析封装。比如,某个字段的值决定后续字段的类型和数量等。
3. 提取结构体中的字段进行序列化
由于通信需要,通信中,通信双方需要设置以及接收参数结构体中的字段。梳理需求,提取出接口:
- 根据字段名称字符串(field name),提取结构体中的字段值:get_field_value_by_name,get_field_value_by_name_as。
- 根据结构体中的字段(field),获取字段的名称字符串(field name):get_field_name_by_field。
- 由于C++ 17 支持不完善,不能提取字段名称字符串,此处使用手动定义字段名-字段偏移地址-字段类型映射表。
定义需要的数据结构。定义支持的字段数据类型枚举 field_type_e,枚举对应的数据类型大小映射表 field_type_sizes,以及字段描述结构体 FieldInfo。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// clang-format off
using field_var_t =std::variant<int8_t, uint8_t, int16_t, uint16_t, int32_t, uint32_t, int64_t, uint64_t, float, double, std::string>;
enum field_type_e : uint8_t {
field_type_i8, field_type_ui8, field_type_i16, field_type_ui16,
field_type_i32, field_type_ui32, field_type_i64, field_type_ui64,
field_type_f32, field_type_f64, field_type_cstrarr,field_type_cstrptr,
field_type_count
};
constexpr std::array<size_t, field_type_count> field_type_sizes = {
sizeof(int8_t), sizeof(uint8_t), sizeof(int16_t), sizeof(uint16_t),
sizeof(int32_t), sizeof(uint32_t), sizeof(int64_t), sizeof(uint64_t),
sizeof(float), sizeof(double), 0, 0
};
// clang-format on
struct FieldInfo {
std::string_view name;
std::size_t offset;
field_type_e type;
};
3.1. 根据输入的结构体实例,以及域字段(field),获取字段名称
1
2
3
4
5
6
7
8
9
10
11
12
13
14
template <typename StructType, typename FieldType, size_t FieldInfoN>
std::string get_field_name_by_field(const StructType& sinst, const FieldType& field,
const std::array<FieldInfo, FieldInfoN>& fields) {
const void* base = static_cast<const void*>(std::addressof(sinst));
const void* field_addr = static_cast<const void*>(std::addressof(field));
const ptrdiff_t offset = static_cast<const char*>(field_addr) - static_cast<const char*>(base);
for (const auto& f : fields) {
if (f.offset == offset) {
return std::string(f.name);
}
}
return {};
}
实现:根据结构体实例的地址,以及字段的地址,计算字段的偏移地址 offset。然后在手动定义的字段描述信息数组 fields 中查找该偏移地址对应的字段名称,并返回。
3.2. 根据字段名称(field name)获取结构体中该字段的值,返回一个 field_var_t
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
template <typename StructType, size_t FieldInfoN>
std::tuple<field_var_t, field_type_e> get_field_value_by_name(const StructType& sinst, const std::string& name,
const std::array<FieldInfo, FieldInfoN>& fields) {
std::string_view sv{name};
// clang-format off
using extractor_t = field_var_t (*)(const char* base, std::size_t off);
auto extract_i8 = [](const char* base, std::size_t off) -> field_var_t { return *reinterpret_cast<const int8_t*>(base + off); };
auto extract_ui8 = [](const char* base, std::size_t off) -> field_var_t { return *reinterpret_cast<const uint8_t*>(base + off); };
auto extract_i16 = [](const char* base, std::size_t off) -> field_var_t { return *reinterpret_cast<const int16_t*>(base + off); };
auto extract_ui16 = [](const char* base, std::size_t off) -> field_var_t { return *reinterpret_cast<const uint16_t*>(base + off); };
auto extract_i32 = [](const char* base, std::size_t off) -> field_var_t { return *reinterpret_cast<const int32_t*>(base + off); };
auto extract_i64 = [](const char* base, std::size_t off) -> field_var_t { return *reinterpret_cast<const int64_t*>(base + off); };
auto extract_ui64 = [](const char* base, std::size_t off) -> field_var_t { return *reinterpret_cast<const uint64_t*>(base + off); };
auto extract_ui32 = [](const char* base, std::size_t off) -> field_var_t { return *reinterpret_cast<const uint32_t*>(base + off); };
auto extract_f32 = [](const char* base, std::size_t off) -> field_var_t { return *reinterpret_cast<const float*>(base + off); };
auto extract_f64 = [](const char* base, std::size_t off) -> field_var_t { return *reinterpret_cast<const double*>(base + off); };
auto extract_cstrarr = [](const char* base, std::size_t off) -> field_var_t {
const char* p = reinterpret_cast<const char*>(base + off);
return std::string(p);
};
auto extract_cstrptr = [](const char* base, std::size_t off) -> field_var_t {
const char* p = *reinterpret_cast<const char* const*>(base + off);
return p ? std::string(p) : std::string();
};
static constexpr std::array<extractor_t, static_cast<size_t>(field_type_count)> extractor_table = {
extractor_t(+extract_i8), extractor_t(+extract_ui8), extractor_t(+extract_i16), extractor_t(+extract_ui16),
extractor_t(+extract_i32), extractor_t(+extract_i64), extractor_t(+extract_ui64), extractor_t(+extract_ui32),
extractor_t(+extract_f32), extractor_t(+extract_f64), extractor_t(+extract_cstrarr), extractor_t(+extract_cstrptr)};
auto pred = [&sv](const FieldInfo& field) { return field.name == sv; };
const auto it = std::find_if(fields.begin(), fields.end(), pred);
if (it != fields.end()) {
const char* base = reinterpret_cast<const char*>(&sinst);
const std::size_t off = it->offset;
const auto idx = static_cast<size_t>(it->type);
if (idx < extractor_table.size()) {
const extractor_t ext = extractor_table[idx];
return {ext(base, off), it->type};
}
}
// clang-format on
return {field_var_t{}, field_type_e{}};
}
实现关键点:根据定义的可解析数据类型,定义对应的提取函数 extractor_t,并建立提取函数表 extractor_table。根据字段名称查找字段信息,然后根据字段类型,调用对应的提取函数,获取字段值。最后,封装为 field_var_t 返回。 字段类型信息,来源于手动定义的字段描述信息数组 fields。根据字段名称查找得到该字段的 field_type_e。
3.3. 另外一个版本:根据字段名称(field name)获取结构体中该字段的值,直接返回类型T
1
2
3
4
5
6
7
8
9
10
template <typename T>
std::optional<T> make_copy(const void* src, std::size_t sz) {
static_assert(std::is_trivially_copyable_v<T>, "T must be trivially copyable");
if (sizeof(T) != sz)
return std::nullopt;
T v;
std::memcpy(&v, src, sz);
return v;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
template <typename StructType, typename FieldType, size_t FieldInfoN>
std::optional<FieldType> get_field_value_by_name_as(const StructType& sinst, const std::string& name,
const std::array<FieldInfo, FieldInfoN>& fields) {
std::string_view sv{name};
auto it = std::find_if(fields.begin(), fields.end(), [&](const FieldInfo& f) {
return f.name == sv;
});
if (it == fields.end())
return std::nullopt;
const char* base = reinterpret_cast<const char*>(&sinst);
const std::size_t off = it->offset;
const field_type_e ft = it->type;
if constexpr (std::is_trivially_copyable_v<FieldType>) {
using extractor_t = std::optional<FieldType> (*)(const char* base, std::size_t off);
// clang-format off
// lambdas must be non-capturing to convert to function pointer
auto ex_i8 = [](const char* base, std::size_t off) -> std::optional<FieldType> { return make_copy<FieldType>(base + off, sizeof(int8_t)); };
auto ex_ui8 = [](const char* base, std::size_t off) -> std::optional<FieldType> { return make_copy<FieldType>(base + off, sizeof(uint8_t)); };
auto ex_i16 = [](const char* base, std::size_t off) -> std::optional<FieldType> { return make_copy<FieldType>(base + off, sizeof(int16_t)); };
auto ex_ui16 = [](const char* base, std::size_t off) -> std::optional<FieldType> { return make_copy<FieldType>(base + off, sizeof(uint16_t)); };
auto ex_i32 = [](const char* base, std::size_t off) -> std::optional<FieldType> { return make_copy<FieldType>(base + off, sizeof(int32_t)); };
auto ex_ui32 = [](const char* base, std::size_t off) -> std::optional<FieldType> { return make_copy<FieldType>(base + off, sizeof(uint32_t)); };
auto ex_i64 = [](const char* base, std::size_t off) -> std::optional<FieldType> { return make_copy<FieldType>(base + off, sizeof(int64_t)); };
auto ex_ui64 = [](const char* base, std::size_t off) -> std::optional<FieldType> { return make_copy<FieldType>(base + off, sizeof(uint64_t)); };
auto ex_f32 = [](const char* base, std::size_t off) -> std::optional<FieldType> { return make_copy<FieldType>(base + off, sizeof(float)); };
auto ex_f64 = [](const char* base, std::size_t off) -> std::optional<FieldType> { return make_copy<FieldType>(base + off, sizeof(double)); };
auto ex_invalid = [](const char* /*base*/, std::size_t /*off*/) -> std::optional<FieldType> { return std::nullopt; };
static const std::array<extractor_t, static_cast<size_t>(field_type_count)> extractors = {
extractor_t(+ex_i8), extractor_t(+ex_ui8), extractor_t(+ex_i16), extractor_t(+ex_ui16),
extractor_t(+ex_i32), extractor_t(+ex_i64), extractor_t(+ex_ui64), extractor_t(+ex_ui32),
extractor_t(+ex_f32), extractor_t(+ex_f64), extractor_t(+ex_invalid), extractor_t(+ex_invalid)};
// clang-format on
const auto idx = static_cast<size_t>(ft);
if (idx < extractors.size()) {
return extractors[idx](base, off);
}
return std::nullopt;
}
// 非 trivially copyable
if constexpr (std::is_same_v<FieldType, std::string>) {
if (ft == field_type_cstrarr) {
const char* p = reinterpret_cast<const char*>(base + off);
return std::string(p);
}
if (ft == field_type_cstrptr) {
const char* const* pp = reinterpret_cast<const char* const*>(base + off);
return pp && *pp ? std::string(*pp) : std::string{};
}
return std::nullopt;
}
return std::nullopt;
}
实现关键点:同样根据定义的可解析数据类型,定义对应的提取函数 extractor_t,并建立提取函数表 extractors。根据字段名称查找字段信息,然后根据字段类型,调用对应的提取函数,获取字段值。最后,封装为 std::optional
返回。 字段类型信息的获取,与函数 get_field_value_by_name 相同。
3.4. 根据字段名称(field name),设置结构体中该字段的值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
template <typename StructType, typename FieldType, size_t FieldInfoN>
inline ptrdiff_t set_field_by_name(StructType& ui, const std::string& name, const FieldType& value,
const std::array<FieldInfo, FieldInfoN>& fields, size_t cstrarr_size = 0) {
std::string_view sv{name};
auto it = std::find_if(fields.begin(), fields.end(), [&](const FieldInfo& f) {
return f.name == sv;
});
if (it == fields.end())
return -1;
char* base = reinterpret_cast<char*>(&ui);
const auto offset = static_cast<ptrdiff_t>(it->offset);
if constexpr (std::is_trivially_copyable_v<FieldType>) {
if (field_type_sizes[it->type] == sizeof(FieldType)) {
std::memcpy(base + offset, &value, sizeof(FieldType));
return offset;
}
return -2; // 类型不匹配
}
if constexpr (std::is_convertible_v<FieldType, const char*>) { // char*, char[], or string literal
if (it->type == field_type_cstrarr) {
if (cstrarr_size == 0)
return -3; // 需指定数组长度
char* arr = base + offset;
std::strncpy(arr, static_cast<const char*>(value), cstrarr_size - 1);
arr[cstrarr_size - 1] = '\0';
return offset;
}
if (it->type == field_type_cstrptr) {
char** pptr = reinterpret_cast<char**>(base + offset);
if (*pptr) {
std::strcpy(*pptr, static_cast<const char*>(value));
return offset;
}
return -4; // 指针为空
}
return -2;
}
// 支持 std::string
if constexpr (std::is_same_v<FieldType, std::string>) {
if (it->type == field_type_cstrarr) {
if (cstrarr_size == 0)
return -3;
char* arr = base + offset;
std::strncpy(arr, value.c_str(), cstrarr_size - 1);
arr[cstrarr_size - 1] = '\0';
return offset;
}
if (it->type == field_type_cstrptr) {
char** pptr = reinterpret_cast<char**>(base + offset);
if (*pptr) {
std::strcpy(*pptr, value.c_str());
return offset;
}
return -4;
}
return -2;
}
return -5; // 不支持的类型
}
3.5. 调用实例
定义映射表如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
static_assert(std::is_standard_layout_v<ps_formation_param_t>, "userinfo not standard layout");
constexpr size_t kFormationParamNamesCount =
boost::pfr::tuple_size<ps_formation_param_t>::value + ps_formation_param_t::kVTableSize - 1;
using namespace struct_reflect;
// clang-format off
constexpr std::array<struct_reflect::FieldInfo, kFormationParamNamesCount> kFormationParamNames = {
FieldInfo{std::string_view("Formation_Desired_Position_Offset_N"),offsetof(ps_formation_param_t, Formation_Desired_Position_Offset_N),field_type_e::field_type_f64},
FieldInfo{std::string_view("Formation_Desired_Position_Offset_E"),offsetof(ps_formation_param_t, Formation_Desired_Position_Offset_E),field_type_e::field_type_f64},
FieldInfo{std::string_view("Formation_Desired_Position_Offset_D"),offsetof(ps_formation_param_t, Formation_Desired_Position_Offset_D),field_type_e::field_type_f64},
FieldInfo{std::string_view("swarmNum"),offsetof(ps_formation_param_t, swarmNum),field_type_e::field_type_ui8},
FieldInfo{std::string_view("hController_k"),offsetof(ps_formation_param_t, hController_k),field_type_e::field_type_f64},
FieldInfo{std::string_view("hController_f"),offsetof(ps_formation_param_t, hController_f),field_type_e::field_type_f64},
FieldInfo{std::string_view("hController_D"),offsetof(ps_formation_param_t, hController_D),field_type_e::field_type_f64},
FieldInfo{std::string_view("coefs_k"),offsetof(ps_formation_param_t, coefs_k),field_type_e::field_type_f64},
FieldInfo{std::string_view("coefs_f"),offsetof(ps_formation_param_t, coefs_f),field_type_e::field_type_f64},
FieldInfo{std::string_view("coefs_u2a"),offsetof(ps_formation_param_t, coefs_u2a),field_type_e::field_type_f64},
FieldInfo{std::string_view("coefs_uff"),offsetof(ps_formation_param_t, coefs_uff),field_type_e::field_type_f64},
FieldInfo{std::string_view("vTable_0"),offsetof(ps_formation_param_t, vTable) + sizeof(double) * 0,field_type_e::field_type_f64},
FieldInfo{std::string_view("vTable_1"),offsetof(ps_formation_param_t, vTable) + sizeof(double) * 1,field_type_e::field_type_f64},
FieldInfo{std::string_view("vTable_2"),offsetof(ps_formation_param_t, vTable) + sizeof(double) * 2,field_type_e::field_type_f64},
FieldInfo{std::string_view("vTable_3"),offsetof(ps_formation_param_t, vTable) + sizeof(double) * 3,field_type_e::field_type_f64},
FieldInfo{std::string_view("vTable_4"),offsetof(ps_formation_param_t, vTable) + sizeof(double) * 4,field_type_e::field_type_f64},
FieldInfo{std::string_view("vTable_5"),offsetof(ps_formation_param_t, vTable) + sizeof(double) * 5,field_type_e::field_type_f64},
FieldInfo{std::string_view("vTable_6"),offsetof(ps_formation_param_t, vTable) + sizeof(double) * 6,field_type_e::field_type_f64},
FieldInfo{std::string_view("vTable_7"),offsetof(ps_formation_param_t, vTable) + sizeof(double) * 7,field_type_e::field_type_f64},
FieldInfo{std::string_view("hSafe"),offsetof(ps_formation_param_t, hSafe),field_type_e::field_type_f64},
FieldInfo{std::string_view("asb_offset_n"),offsetof(ps_formation_param_t, asb_offset_n),field_type_e::field_type_f64},
FieldInfo{std::string_view("asb_offset_e"),offsetof(ps_formation_param_t, asb_offset_e),field_type_e::field_type_f64},
FieldInfo{std::string_view("L1"),offsetof(ps_formation_param_t, L1),field_type_e::field_type_f64},
FieldInfo{std::string_view("kVel"),offsetof(ps_formation_param_t, kVel),field_type_e::field_type_f64},
FieldInfo{std::string_view("robustLat"),offsetof(ps_formation_param_t, robustLat),field_type_e::field_type_f64},
FieldInfo{std::string_view("asbController_kPos"),offsetof(ps_formation_param_t, asbController_kPos),field_type_e::field_type_f64},
FieldInfo{std::string_view("asbController_kAsb"),offsetof(ps_formation_param_t, asbController_kAsb),field_type_e::field_type_f64},
FieldInfo{std::string_view("asbController_tol1"),offsetof(ps_formation_param_t, asbController_tol1),field_type_e::field_type_f64},
FieldInfo{std::string_view("asbController_tol2"),offsetof(ps_formation_param_t, asbController_tol2),field_type_e::field_type_f64},
FieldInfo{std::string_view("asbController_r1"),offsetof(ps_formation_param_t, asbController_r1),field_type_e::field_type_f64},
FieldInfo{std::string_view("asbController_r2"),offsetof(ps_formation_param_t, asbController_r2),field_type_e::field_type_f64},
FieldInfo{std::string_view("fmtController_kLat"),offsetof(ps_formation_param_t, fmtController_kLat),field_type_e::field_type_f64},
FieldInfo{std::string_view("fmtController_kProj"),offsetof(ps_formation_param_t, fmtController_kProj),field_type_e::field_type_f64},
FieldInfo{std::string_view("fmtWpStartIdx"),offsetof(ps_formation_param_t, fmtWpStartIdx),field_type_e::field_type_ui16},
FieldInfo{std::string_view("takeIdx"),offsetof(ps_formation_param_t, takeIdx),field_type_e::field_type_ui16},
FieldInfo{std::string_view("returnIdx"),offsetof(ps_formation_param_t, returnIdx),field_type_e::field_type_ui16},
FieldInfo{std::string_view("Vn"),offsetof(ps_formation_param_t, Vn),field_type_e::field_type_f64},
FieldInfo{std::string_view("rm"),offsetof(ps_formation_param_t, rm),field_type_e::field_type_f64},
};
// clang-format on
在此,对结构体作限制:要求结构体以及其字段均为标准布局类型(standard layout type),以确保字段偏移地址的正确性。
其他代码省略。