如何在C语言中正确存储含指针成员的结构体到文件?
解决C语言中含指针的结构体序列化/反序列化问题
你说得对,直接把结构体写入文件只会保存指针的内存地址——这些地址在程序下次运行时完全无效,自然会导致段错误。要解决这个问题,我们需要手动序列化指针指向的实际数据,同时还要记录数据的长度信息(因为C无法自动追踪动态分配内存的大小)。下面是具体的实现步骤和代码示例:
前提:明确数据的长度信息
首先,你需要知道每个指针指向的数据的维度/长度:
- 对于
char **one(二维字符串数组):需要知道总行数,以及每行字符串的长度(或者直接用字符串的\0作为结束标记,但提前存长度更可靠) - 对于
mytype **two(二维自定义类型数组):需要知道总行数和每行的元素个数 - 对于
mytype2 *three(一维自定义类型数组):需要知道元素总个数
假设我们在获取结构体时,同时记录这些长度信息(你可以修改get_struct函数返回这些信息,或者用全局变量/额外的结构体存储,这里我们用额外的变量来传递)。
步骤1:实现结构体的保存(序列化)
我们需要按顺序写入:元数据(长度信息)→ 每个指针指向的实际数据。
示例代码:
#include <stdio.h> #include <stdlib.h> #include <string.h> // 先定义你的自定义类型(假设mytype和mytype2是这样的) typedef int mytype; typedef float mytype2; typedef struct myStruct { char **one; mytype **two; mytype2 *three; } MyStruct; // 假设get_struct会返回结构体,同时我们需要获取这些长度信息: void get_struct(MyStruct *s, int *rows_one, int *rows_two, int *cols_two, int *count_three) { // 这里模拟动态分配数据 *rows_one = 2; s->one = malloc(*rows_one * sizeof(char*)); s->one[0] = strdup("Hello"); s->one[1] = strdup("World"); *rows_two = 2; *cols_two = 3; s->two = malloc(*rows_two * sizeof(mytype*)); for (int i = 0; i < *rows_two; i++) { s->two[i] = malloc(*cols_two * sizeof(mytype)); for (int j = 0; j < *cols_two; j++) { s->two[i][j] = i * 10 + j; } } *count_three = 4; s->three = malloc(*count_three * sizeof(mytype2)); for (int i = 0; i < *count_three; i++) { s->three[i] = i * 0.5f; } } // 保存结构体到文件的函数 int save_struct(MyStruct *s, const char *filename, int rows_one, int rows_two, int cols_two, int count_three) { FILE *fp = fopen(filename, "wb"); // 用二进制模式打开! if (!fp) { perror("Failed to open file for writing"); return -1; } // 第一步:写入元数据(所有长度信息) fwrite(&rows_one, sizeof(int), 1, fp); fwrite(&rows_two, sizeof(int), 1, fp); fwrite(&cols_two, sizeof(int), 1, fp); fwrite(&count_three, sizeof(int), 1, fp); // 第二步:写入char **one的数据 for (int i = 0; i < rows_one; i++) { // 先写入字符串长度(包括'\0'),避免读取时依赖'\0' size_t str_len = strlen(s->one[i]) + 1; fwrite(&str_len, sizeof(size_t), 1, fp); fwrite(s->one[i], sizeof(char), str_len, fp); } // 第三步:写入mytype **two的数据 for (int i = 0; i < rows_two; i++) { fwrite(s->two[i], sizeof(mytype), cols_two, fp); } // 第四步:写入mytype2 *three的数据 fwrite(s->three, sizeof(mytype2), count_three, fp); fclose(fp); return 0; }
步骤2:实现结构体的读取(反序列化)
读取时需要按保存的顺序:先读元数据→分配对应内存→读取实际数据到分配的内存中。
示例代码:
// 读取结构体从文件的函数 int load_struct(MyStruct *s, const char *filename, int *rows_one, int *rows_two, int *cols_two, int *count_three) { FILE *fp = fopen(filename, "rb"); // 二进制模式读取! if (!fp) { perror("Failed to open file for reading"); return -1; } // 第一步:读取元数据 fread(rows_one, sizeof(int), 1, fp); fread(rows_two, sizeof(int), 1, fp); fread(cols_two, sizeof(int), 1, fp); fread(count_three, sizeof(int), 1, fp); // 第二步:读取char **one s->one = malloc(*rows_one * sizeof(char*)); for (int i = 0; i < *rows_one; i++) { size_t str_len; fread(&str_len, sizeof(size_t), 1, fp); s->one[i] = malloc(str_len * sizeof(char)); fread(s->one[i], sizeof(char), str_len, fp); } // 第三步:读取mytype **two s->two = malloc(*rows_two * sizeof(mytype*)); for (int i = 0; i < *rows_two; i++) { s->two[i] = malloc(*cols_two * sizeof(mytype)); fread(s->two[i], sizeof(mytype), *cols_two, fp); } // 第四步:读取mytype2 *three s->three = malloc(*count_three * sizeof(mytype2)); fread(s->three, sizeof(mytype2), *count_three, fp); fclose(fp); return 0; }
步骤3:测试使用,以及内存释放
别忘了使用完结构体后要释放所有动态分配的内存,避免泄漏:
// 释放结构体内存的函数 void free_struct(MyStruct *s, int rows_one, int rows_two, int count_three) { // 释放one for (int i = 0; i < rows_one; i++) { free(s->one[i]); } free(s->one); // 释放two for (int i = 0; i < rows_two; i++) { free(s->two[i]); } free(s->two); // 释放three free(s->three); } // 主函数测试 int main() { MyStruct s, t; int rows_one, rows_two, cols_two, count_three; // 获取结构体 get_struct(&s, &rows_one, &rows_two, &cols_two, &count_three); // 保存到文件 if (save_struct(&s, "file.dat", rows_one, rows_two, cols_two, count_three) != 0) { exit(EXIT_FAILURE); } // 释放原结构体内存 free_struct(&s, rows_one, rows_two, count_three); // 从文件读取结构体 if (load_struct(&t, "file.dat", &rows_one, &rows_two, &cols_two, &count_three) != 0) { exit(EXIT_FAILURE); } // 调用你的work_with_struct函数 work_with_struct(t); // 读取后释放内存 free_struct(&t, rows_one, rows_two, count_three); return 0; } // 你的work_with_struct函数 void work_with_struct(MyStruct s){ char ** xone = s.one; mytype **xtwo = s.two; mytype2 *xthree = s.three; // 这里可以测试输出数据是否正确 printf("one[0]: %s\n", xone[0]); printf("one[1]: %s\n", xone[1]); for (int i = 0; i < 2; i++) { for (int j = 0; j < 3; j++) { printf("two[%d][%d]: %d ", i, j, xtwo[i][j]); } printf("\n"); } for (int i = 0; i < 4; i++) { printf("three[%d]: %.1f\n", i, xthree[i]); } }
关键注意事项
- 必须用二进制模式打开文件(
"wb"和"rb"),避免文本模式下的换行符转换等问题,导致数据错位。 - 元数据的顺序必须和保存时一致,读取时如果顺序错了,所有数据都会乱掉。
- 内存管理要仔细:分配的每一块内存都要对应释放,尤其是二维数组的行指针和每行的内存。
- 如果你的
mytype或mytype2是复杂结构体(而非基本类型),那你需要对它们也做序列化/反序列化(如果它们内部也有指针的话)——如果只是基本类型(int、float等),直接fwrite/fread就行。
内容的提问来源于stack exchange,提问作者user13963485
相关产品推荐
相关产品推荐

