C 语言序列化和反序列化

一、核心概念:什么是序列化与反序列化?

在计算机科学中,序列化(Serialization)反序列化(Deserialization) 是数据持久化与传输的核心技术。

  • 序列化:将内存中的数据结构(如结构体、数组等)转换为二进制字节流的过程。这个字节流可以存储到文件、数据库,或通过网络传输到其他设备。
  • 反序列化:将二进制字节流还原为内存中可用数据结构的过程,是序列化的逆操作。

典型应用场景

  • 数据持久化:将程序运行时的临时数据(如用户信息、配置参数)保存到文件,下次启动时恢复。
  • 网络通信:不同进程/设备间通过网络传输数据时,需将数据转换为无格式的二进制流(避免文本协议的解析开销)。
  • 跨平台协作:确保不同系统(如Windows/Linux)间数据格式兼容(需注意字节序问题)。

二、C 语言实现序列化与反序列化

C 语言作为面向过程的语言,没有内置的序列化库,但可以通过文件操作函数(如 fwrite/fread)直接操作二进制数据,实现轻量级序列化。以下是完整实现与详细解析。

1. 数据结构定义

首先定义需要序列化的目标结构体。本例以学生信息为例:

1
2
3
4
5
typedef struct {
char name[50]; // 姓名(固定长度字符串)
int age; // 年龄(整型)
float score; // 分数(浮点型)
} Student;

选择固定长度的字符数组存储字符串,避免动态内存(如 char* name)带来的序列化复杂度(需额外记录指针地址和内存长度)。

2. 序列化函数实现

序列化函数的核心是将结构体内存块整体写入二进制文件。

1
2
3
4
5
6
7
8
9
10
void serialize(Student *student, const char *filename) {
FILE *file = fopen(filename, "wb"); // "wb":二进制写模式(覆盖模式)
if (file == NULL) { // 检查文件是否成功打开
perror("无法打开文件"); // 打印错误信息(如权限不足、路径错误)
return;
}
// 写入操作:参数依次为 数据指针、单元素大小、元素数量、文件指针
fwrite(student, sizeof(Student), 1, file);
fclose(file); // 关闭文件释放资源
}
  • 关键函数:fwrite的作用是将内存中的一段连续字节写入文件。
    • 第一个参数 student:指向待写入数据的指针(结构体内存块的起始地址)。
    • 第二个参数 sizeof(Student):单次写入的元素大小(整个结构体的字节长度)。
    • 第三个参数 1:仅写入1个这样的元素(即整个结构体)。
    • 第四个参数 file:目标文件指针。

3. 反序列化函数实现

反序列化函数从二进制文件中读取字节流,并还原为结构体实例。

1
2
3
4
5
6
7
8
9
10
void deserialize(Student *student, const char *filename) {
FILE *file = fopen(filename, "rb"); // "rb":二进制读模式
if (file == NULL) {
perror("无法打开文件");
return;
}
// 读取操作:参数含义与 fwrite 一致
fread(student, sizeof(Student), 1, file);
fclose(file);
}
  • 关键函数freadfwrite 是一对“镜像”函数,负责从文件读取字节流到内存。

4. 主函数验证

主函数演示完整的序列化-反序列化流程:

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
int main() {
// 初始化原始数据
Student original_student;
strncpy(original_student.name, "Alice", sizeof(original_student.name)-1); // 避免字符串溢出
original_student.name[sizeof(original_student.name)-1] = '\0'; // 手动添加终止符
original_student.age = 20;
original_student.score = 95.5;

// 序列化:写入文件
const char *filename = "student_data.bin";
serialize(&original_student, filename);
printf("序列化完成,数据已写入 %s
", filename); // 输出提示(无空行)

// 反序列化:读取文件
Student deserialized_student;
deserialize(&deserialized_student, filename);
printf("反序列化完成,读取的数据如下:
");
printf("姓名: %s
", deserialized_student.name); // 输出:Alice
printf("年龄: %d
", deserialized_student.age); // 输出:20
printf("分数: %.1f
", deserialized_student.score); // 输出:95.5

return 0;
}
  • 注意事项 :
  • 字符串赋值使用 strncpy 而非 strcpy,避免目标数组溢出(name 数组长度为50,需预留终止符空间)。
  • 手动设置字符串终止符 \0,确保 printf 正确输出(strncpy 不会自动添加)。

三、方案局限性与优化方向

本例的实现适用于固定内存布局的结构体,但存在以下限制:

  1. 动态内存不友好:若结构体包含指针(如 char* name),fwrite 会仅保存指针地址而非实际字符串内容,反序列化后指针失效。
  2. 跨平台兼容性:不同系统的字节序(大端/小端)可能导致浮点数或整型数据解析错误(需手动处理字节序)。
  3. 可读性差:二进制文件无法直接查看内容(需借助工具如 hexdump)。

四、完整代码

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
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

// 定义学生结构体
typedef struct {
char name[50]; // 姓名(固定长度)
int age; // 年龄
float score; // 分数
} Student;

// 序列化:将结构体写入二进制文件
void serialize(Student *student, const char *filename) {
FILE *file = fopen(filename, "wb");
if (!file) {
perror("无法打开文件");
return;
}
fwrite(student, sizeof(Student), 1, file); // 写入整个结构体内存块
fclose(file);
}

// 反序列化:从二进制文件读取结构体
void deserialize(Student *student, const char *filename) {
FILE *file = fopen(filename, "rb");
if (!file) {
perror("无法打开文件");
return;
}
fread(student, sizeof(Student), 1, file); // 读取整个结构体内存块
fclose(file);
}

int main() {
// 初始化原始数据
Student original = {.name = "Alice", .age = 20, .score = 95.5}; // C99指定初始化

// 序列化
const char *filename = "student_data.bin";
serialize(&original, filename);
printf("序列化完成,数据已写入 %s
", filename);

// 反序列化
Student restored;
deserialize(&restored, filename);
printf("反序列化结果:
");
printf("姓名: %s
年龄: %d
分数: %.1f
", restored.name, restored.age, restored.score);

return 0;
}