C语言实现含堆指针的结构体数组与缓冲区的序列化和反序列化方法
C语言含堆指针结构体数组的序列化与反序列化实现
核心痛点是结构体内部的
ptr是堆内存指针,直接拷贝结构体只会保存指针的虚拟地址,反序列化后该地址完全无效,因此必须将ptr指向的实际数据也写入序列化缓冲区,重建时再重新分配堆内存拷贝数据。
实现思路
序列化步骤
- 第一步:计算总缓冲区大小 = 存储数组长度的int字节 + 所有
struct Foo普通成员的大小之和 + 所有Foo::ptr指向的内存大小之和 - 第二步:申请对应大小的堆缓冲区
- 第三步:先写入数组总长度到缓冲区头部
- 第四步:遍历每个结构体元素,依次写入结构体的普通成员(a、b、size_of_ptr、d),紧接着写入
ptr指向的实际数据,不断偏移缓冲区指针直到所有元素处理完成
反序列化步骤
- 第一步:从缓冲区头部读取数组总长度
n_objs - 第二步:申请
sizeof(struct Foo) * n_objs的内存作为新的结构体数组 - 第三步:遍历每个元素,先读取普通成员赋值给新结构体,再根据读到的
size_of_ptr申请对应大小的堆内存给新结构体的ptr,最后把缓冲区中存储的ptr数据拷贝到新申请的堆内存中 - 第四步:返回重建完成的结构体数组指针
完整代码示例
#include <stdlib.h> #include <string.h> struct Foo { int a; int b; void* ptr; // 指向堆分配内存 int size_of_ptr; double d; }; // 序列化函数,返回堆分配的缓冲区,使用完后需要free void* serialize(struct Foo* f_arr, int n_objs) { // 计算总缓冲区大小 size_t total_size = sizeof(int); // 开头存储数组元素个数 for (int i = 0; i < n_objs; i++) { // 累加普通成员大小(排除ptr指针本身) total_size += sizeof(struct Foo) - sizeof(void*); // 累加ptr指向的实际数据大小 total_size += f_arr[i].size_of_ptr; } char* buf = malloc(total_size); if (!buf) return NULL; // 写入元素个数 *(int*)buf = n_objs; char* offset_ptr = buf + sizeof(int); for (int i = 0; i < n_objs; i++) { // 逐个写入普通成员,避免结构体对齐问题 *(int*)offset_ptr = f_arr[i].a; offset_ptr += sizeof(int); *(int*)offset_ptr = f_arr[i].b; offset_ptr += sizeof(int); *(int*)offset_ptr = f_arr[i].size_of_ptr; offset_ptr += sizeof(int); *(double*)offset_ptr = f_arr[i].d; offset_ptr += sizeof(double); // 写入ptr指向的实际数据 memcpy(offset_ptr, f_arr[i].ptr, f_arr[i].size_of_ptr); offset_ptr += f_arr[i].size_of_ptr; } return buf; } // 反序列化函数,返回堆分配的结构体数组,使用完需要先逐个释放ptr再释放数组本身 struct Foo* deserialize(void* buf) { char* offset_ptr = (char*)buf; // 读取数组元素个数 int n_objs = *(int*)offset_ptr; offset_ptr += sizeof(int); struct Foo* new_arr = malloc(sizeof(struct Foo) * n_objs); if (!new_arr) return NULL; for (int i = 0; i < n_objs; i++) { // 读取普通成员 new_arr[i].a = *(int*)offset_ptr; offset_ptr += sizeof(int); new_arr[i].b = *(int*)offset_ptr; offset_ptr += sizeof(int); new_arr[i].size_of_ptr = *(int*)offset_ptr; offset_ptr += sizeof(int); new_arr[i].d = *(double*)offset_ptr; offset_ptr += sizeof(double); // 为ptr分配堆内存 new_arr[i].ptr = malloc(new_arr[i].size_of_ptr); if (!new_arr[i].ptr) { // 分配失败时释放已申请的内存,避免泄漏 for (int j = 0; j < i; j++) { free(new_arr[j].ptr); } free(new_arr); return NULL; } // 拷贝实际数据到新分配的堆内存 memcpy(new_arr[i].ptr, offset_ptr, new_arr[i].size_of_ptr); offset_ptr += new_arr[i].size_of_ptr; } return new_arr; }
注意事项
- 上述实现手动逐个拷贝成员,规避了结构体字节对齐带来的偏移问题,跨编译环境也可正常使用
- 反序列化返回的结构体数组使用完后,需要先逐个释放每个元素的
ptr指针,再释放结构体数组本身,避免内存泄漏 - 原业务代码中
Foo* f_arr = (Foo) malloc(sizeof Foo * n_objs_);存在类型转换错误,应改为Foo* f_arr = (Foo*) malloc(sizeof(struct Foo) * n_objs_);
内容的提问来源于stack exchange,提问作者Trex
相关产品推荐
相关产品推荐

