读书人

【c++探员的故事】【资料校验篇】001

发布时间: 2012-10-19 16:53:37 作者: rapoo

【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成员。

==============================

未完待续,写了这个东西之后,才知道写这种东西多么地浪费时间。


明天继续吧,请多多交流。










读书人网 >C++

热点推荐