

C语言陷阱与技巧第12节,重要数据怎么保存?如何判断数据是否损坏?
source link: https://blog.popkx.com/c-language-traps-and-techniques-section-12-how-to-save-important-data-how-to-judge-whether-the-data-is-damaged/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

C语言中的结构体是非常有用的复合数据类型,正是有了结构体,C语言在描述复杂问题时才能够得心应手。事实上,当初 Dennis Ritchie 开发C语言用于替换 B 语言,其中一个主要原因就是 B 语言不支持“结构体”式数据结构。
C语言中的结构体非常有用
例如,利用C语言描述人的身高、体重、年龄、性别、姓名时,使用结构体时非常方便的,相关C语言代码可以如下定义:
struct person{
float height;
float weight;
int age;
char gender;
char name[128];
};
上面的C语言代码定义了 person 结构体,用于描述要求统计的每个人信息。一般来说,统计信息常常需要记录在磁盘里,如果这些信息比较重要,往往还需要记录不止一份。这样在数据损坏时,可以从备份将损坏数据修复。
如何判断数据是否损坏
但是,C语言程序怎么能知道存在磁盘里的数据有没有损坏呢?这其实就需要借助于校验了,一个非常常用的校验方法是 crc32 校验。crc32 校验可以根据一段长度(若干字节)的数据生成一个 32bit 的数,理想情况下,数据不同,生成的校验值也不同。
所以上面的 person 结构体最好加上一个成员 crc32,相关C语言代码如下,请看:
struct person{
float height;
float weight;
int age;
char gender;
char name[128];
int crc32;
};
person 结构体假设 int 类型占 4 字节内存空间。
这样在记录数据的时候,先计算出这段数据的 crc32 校验值,然后将数据和 crc32 校验值一起存储。以后读取数据时,可以再计算一次 crc32 校验值,并与原先记录的旧 crc32 校验值比较,若相等,则可以认为数据没有损坏;若不相等,就说明数据损坏,可以启动数据修复逻辑了。
上面这种判断数据是否损坏的方法,其实是有可能误判(现实与理想总是有差距)的,但是几率比较小,因此 crc32 仍然是一个不错的数据校验方法。
怎样计算结构体的校验值
计算 crc32 的方法不是本节的重点,而且网络上资源很多。这里直接假设获取一段数据的 crc32 校验值的函数的原型如下,请看C语言代码:
int get_crc32(char *buf, int size);
此时,计算 person 的校验值的C语言代码似乎可以这么写:
char buf[256];
int size = 0;
memcpy(buf, s.height);
size += sizeof(s.height);
memcpy(buf+size, s.weight);
...
size += sizeof(gender);
memcpy(buf+size, s.name);
size += sizeof(name);
s.crc32 = get_crc32(buf, size);
想想看,为什么不能直接这么计算 crc32 校验值呢:s.crc32 = get_crc32(&s, sizeof(struct test s));
显然, 这么计算太麻烦了,若是结构体的成员非常多,估计要把C语言程序员累死。而且,要是以后为结构体添加新成员,或者删除旧成员,这段计算 crc32 校验值的C语言代码也需修改,可见,这样计算 crc32 校验值的代码维护起来也是非常的麻烦,还容易出错。
因此,计算结构体的校验值的代码一般都不像上面那样写,那该怎么写呢?如果能够直接获取 crc32 成员在结构体 test 中的偏移量 offset,那计算校验值的C语言代码就很好写了:
s.crc32 = get_crc32(&s, offset);
那么,offset 等于多少呢?很多C语言初学者会认为:
offset = sizeof(s.height)+sizeof(s.height)+...+sizeof(name);
姑且不管这样计算 crc32 校验值一样会带来代码维护困难、容易出错又麻烦的问题。这样计算的 offset 都不等于 crc32 成员在结构体 test 中的偏移量,因此这样计算校验值是不合适的。
还记得结构体的“内存对齐”相关的陷阱吗?(可以参考我的专栏《C语言经典面试题详解》)
计算结构体某成员偏移量的小技巧
我们都知道,C语言中结构体的各个成员在内存中其实也是先后存储的,结构体 s 的成员 crc32 肯定是排在 s 之后的,因此计算结构体中某个成员的偏移量,其实可以采用“地址相减法”:
offset = &s.crc32 - &s;
s.crc32 = get_crc32(&s, offset);
知道原理了,我们完全可以自己定义一个宏,用于计算结构体某成员在结构体中的偏移量,相关C语言代码如下,请看:
#define offset(type, v) (size_t)(&(((type*)0)->v))
既然结构体成员地址减去结构体地址就等于该成员的偏移量,那如果结构体地址为 0,该成员的地址就恰好等于它在结构体中的偏移量了,现在我们编写测试用例,相关C语言代码如下,请看:
#include <stdio.h>
struct test{
char a;
double b;
int c;
};
#define offset(type, v) (size_t)(&(((type*)0)->v))
int main()
{
struct test s;
printf("%ld %ld %ld\n", offset(struct test, a),
offset(struct test, b),
offset(struct test, c));
return 0;
}
编译并执行这段C语言代码,得到如下结果:
# gcc t.c
# ./a.out
0 8 16
一切与预期一致。现在利用 offset 宏计算结构体 person 的校验值就方便了,请看下面的C语言代码:
s.crc32 = get_crc32(&s, offset(struct test, crc32));
而且,无论以后如何调整 person 的成员,删除也好,新增也好,只要保证 crc32 是它的最后一个成员,计算校验值的代码就无需改动,这样的C语言代码维护起来也是非常的省心的。
在C语言程序开发中,若需记录在磁盘中的数据非常重要,则应该保存不止一份,这样才能在尽可能的确保数据不损坏。关于如何判断数据是否损坏,本节介绍了一种常用的 crc32 校验法,在此基础上,讨论了一种计算结构体成员偏移量的方法,并将其封装成宏,特别有利于之后C语言代码的维护。
Recommend
-
15
头文件是C语言的一个重要组成部分,这种类型的文件名一般以 .h 结尾,h 表示 header,因此被称为“头文件”。头文件里一般存放公开的函数原型,数据类型等内容,其他模块需要使用这些函数或者数据类型时,只需包含相应头文件即可。相信读者大都使用过C语言...
-
10
上一节讨论了C语言的 union 语法的一个使用场景,读者应该发现,union 成员共享一块内存的特性使得C语言程序员能够写出更加节约资源的程序。不过也有读者在看了上一节文章后,评论或者私信说了一些关于C语言 union 的特性,这些回复有些是不严谨的。如果...
-
21
C语言陷阱与技巧第33节,联合体union的使用场景是什么?它能解决什么问题?为什么要使用union语法? ...
-
15
在C语言程序开发中,遇到复杂问题需要描述时,最常使用的就是结构体了。事实上,如果某个函数的参数比较多,并且这些参数被使用的频率比较高,为了C语言代码的简洁,也常将这些参数封装为结构体。“重复的C语言代码”如果函数的参数比较多,很容...
-
26
在C语言程序开发中,一些比较成熟的库函数常常会被使用。毕竟,如果手边就有不错的“轮子”可以用,没有程序员愿意再花费精力凭空造一个轮子出来。奇怪的 void* 指针事实上,C语言标准库提供了非常丰富的成熟函数供程序员使用,不过不知道读者注...
-
11
前面的文章曾讨论,为了写出适应性更广的C语言程序,程序员考虑问题时应面面俱到。例如,在C语言程序中调用 open() 函数尝试打开文件时,应考虑到文件是否存在,当前程序是否有足够权限等情况。在打开文件失败时,需要做相应的错误处理,这样才能让程序的稳定性...
-
13
C语言陷阱与技巧第29节,很多程序员不知道,C语言也能“继承”父类 发表于 201...
-
12
“拷贝”的重要性在C语言程序开发中,函数的参数有时候需要拷贝一份才能安全的执行功能,下面是一个经常在面试题中出现的典型例子,请看:double pow2(double *x) { return (*x)*(*x); } 上面这段C语言代码很简单,...
-
8
C语言陷阱与技巧第13节,1字节(Byte)一定等于8位(bit)吗?C语言怎么操作位? 发表于...
-
6
如何长时间保存重要数据? 我大学毕业时把所有资料刻录成几张 dvd,才几年就发现读取不了了,而我老爸读大学时候的笔记本,几十年后仍保存完好。我前几年保存在移动硬盘里的照片,因为搬家时摔了一次,完全毁坏了,但是我家里小时候的相册却...
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK