【c++探员的故事】【文件校验篇】001
背景:老板让我做一个文件的断电恢复。
当时候就想,那就做个类似通信里面的校验码什么的东西呗。
我就在想怎么去设计这个校验,crc,md5?这两种东西,自己之前从没有接触过。
老板说:其实不用这么麻烦,你随便写个简单的校验就好了。
那么这个故事,就是设计过程中,遇到的一些故事。故事很曲折,但也很有趣。
---------------------------------
接到任务之后,产生了这么一个思路。
读取验证的过程如下:
1 打开文件
2 读取文件验证码a
3 读取文件中的数据
4 根据数据计算验证码b
5 比较两个验证码
5.1 如果相同,就加载该文件中的数据
5.2 如果不同,那就夹在备份文件的数据
6 关闭文件
保存文件的过程如下:
1 打开文件
2 计算出数据的验证码
3 保存验证码
4 保存数据,并关闭文件
5 打开备份文件
6 保存数据,并关闭文件
-------------------------
案件1:
当我加载文件的时候,因为两个验证码不相等,总是加载备份文件。
这是咋回事呢?经过深入的调查,原来:在读取验证那个过程中,每次从文件数据里面计算出来的验证码b都是不一样的。
为什么都是同一个数据,其计算出来的验证码会不一样呢?
下面就进行这个案件的探索:
请看代码:
----------------
class A
{
public:
int n;
char c;
};
int main()
{
A a;
a.n=3;
a.c='3';
int n=0;//用于保存验证码
char * c=(char*)&a;
for(int i=0;i<sizeof(a);i++)
{
n+=*c;
c++;
}
cout<<n<<endl;
cin>>n;
main();
}
------------------------
结果:每次输出的n的值都是不一样的。
之前,我觉得很奇怪,当时我就想到了memset,因为在32位环境下该结构是8字节大小。那就注定有3字节的填充位。
虽然我可以控制其中5字节的数据(int 占4字节,char 占1字节),但是还有其他3位就不能保证了。
由于哥没有正确地使用这个函数(记错了参数的位置)导致了实验的失败。
发现这个问题之后,将代码改成以下这样,实验就获得了成功。
----------------
class A
{
public:
int n;
char c;
};
int main()
{
A a;
memset((void*)&a,0,sizeof(a));//之前错写为memset((void*)&a,sizeof(a),0);
a.n=3;
a.c='3';
int n=0;//用于保存验证码
char * c=(char*)&a;
for(int i=0;i<sizeof(a);i++)
{
n+=*c;
c++;
}
cout<<n<<endl;
cin>>n;
main();
}
------------------------
问题顺利解决,跟哥想的完全一样,毕竟哥也算个侦探麻。由于用错了memset这个函数,给故事添加了不少曲折。
上了csdn论坛提问。
有人说:不能在main()函数里调mian();
这个问题争论起来很麻烦,我认为可以,请你给我个会造成错误的反例看。
当初这样写,也就是为了方便。你可以不断地比较输出的n的值。
那我就把替换成了死循环。
代码如下:
----------------
class A
{
public:
int n;
char c;
};
int main()
{
while(1)
{//循环开始
A a;
a.n=3;
a.c='3';
int n=0;//用于保存验证码
char * c=(char*)&a;
for(int i=0;i<sizeof(a);i++)
{
n+=*c;
c++;
}
cout<<n<<endl;
cin>>n;
}//循环结束,不是我代码风格差,这个是在博客上手写的,不能用tab键,所以不能对齐
}
--------------------结果这个试验,不小心触发了新的剧情。
你可以试试,这个每次输出的n的值都一样。
为什么,这个程序会产生令人蒙蔽的结果。
冷静的探员分析了一下,估计有以下两种可能:
1 编译器对 对象a进行了优化?理由是a反复使用。
2 难道读写的都是同一块地址。
我对a进行了volatile修饰,结果还是一样。
可能1被排除
我在没有使用volatile的情况下,输出了a的地址,结果发现的是同一个地址。
----------------
class A
{
public:
int n;
char c;
};
int main()
{
while(1)
{//循环开始
A a;
cout<<&a<<endl;//输出a地址
a.n=3;
a.c='3';
int n=0;//用于保存验证码
char * c=(char*)&a;
for(int i=0;i<sizeof(a);i++)
{
n+=*c;
c++;
}
cout<<n<<endl;
cin>>n;
}//循环结束,不是我代码风格差,这个是在博客上手写的,不能用tab键,所以不能对齐
}
--------------------那这个结果就可以解释了,因为读的是同一块地址。
对象a中的5个字节的内容被你控制了(也就是给a进行赋值的过程),其他的3个字节的内容因为没有改变。所以每次都一样。
如果再继续探索下去,为什么每次的地址都是一样的呢。
这刚好解释了入栈出栈的过程。
然后以下这个代码,也能说明栈的增长方向。
----------------
class A
{
public:
int n;
char c;
};
int main()
{
A a;
cout<<&a<<endl;//比较一下每次输出的地址值吧
a.n=3;
a.c='3';
int n=0;//用于保存验证码
char * c=(char*)&a;
for(int i=0;i<sizeof(a);i++)
{
n+=*c;
c++;
}
cout<<n<<endl;
cin>>n;
main();
}
------------------很有趣吧。
但是故事为什么非要那么曲折,在我确定我已经能对数据里面的所有位进行完全性的控制(memset)后,我的程序还是出现了问题。
原因是这样的:我的类的成员里有个string成员。
==============================
未完待续,写了这个东西之后,才知道写这种东西多么地浪费时间。
明天继续吧,请多多交流。