我们操作系统的大作业是模拟一个文件系统,我的想法是搞一个二进制文件出来,然后针对这个二进制文件模拟操作系统的管理方式。但是在 login 的时候,我就遇到了麻烦。这个bug我查到凌晨三点才发现,最后真是哭笑不得,觉得很有必要写一写。bug虽然很蠢,但是对我的教训却是很深啊。
我想要实现的是,将用户id和名字密码等信息永久地保存在一个二进制文件中。用的文件操作函数是 fwrite函数 和 fread函数,将一个用户结构体直接写到文件里面去,用到的时候再读出来。我的 user 结构体是这样写的:
1 2 3 4 5 6 |
struct user { unsigned int user_id; string user_name; string user_psw; }; |
然后就发现了这样的错误:登陆之前必须初始化文件,然后添加用户,删除用户,切换用户登陆都没有问题。但是一旦退出再次进行调试,保准会运行异常挂掉。不能做到永久存储。
再扯一下我试过的调试方法吧。因为对二进制读写用的不是很多,所以就以为是文件读写有问题,于是查了一堆fwrite和fread的用法和参考,搞了一堆demo来测试——都没问题。然后以为是流程问题,搞了堆输出来测试,也没问题。后来才用了跟踪调试,老老实实一步步跟进去,才发现了问题。
答案就是:string类型存放的其实是一个指针,并不是实际的字符串。而fwrite是将整个结构体作为二进制写进去的,也就是说存了个指针。一旦退出程序,下次启动,这个string存放的地址就不再是用户名和密码了,变得毫无意义。正确地方式是,使用char型数组来存放。如下:
1 2 3 4 5 6 |
struct user { unsigned int user_id; char user_name[USER_CHAR]; char user_psw[USER_CHAR]; }; |
发现这个的时候,凌晨三点,周围很静。我一下子想起来一周之前在机房问Wii要用什么语言写,他说C,我嘲笑说pure C? low。现在终于吃到了苦头。对着屏幕傻笑了好一会儿。
教训总结:
- 以为STL会带来很多便捷,就想当然地使用了C++来写,太愚蠢了。最近看的一些系统的书,都提到要使用C,甚至是汇编。即使是pure C,从0实现的时候,也不能使用printf这种函数,甚至还要针对gcc等编译器进行优化,相当底层。我都有点觉得,汇编才是最好的编程语言了!
- 一定要区分C和C++的区别。这点仁者见仁吧,我以前把秋波老师的话奉为信条,他就说过:我管你是C还是C++,能写出程序来就行了,老朱也说过,除了io的时候用C,别的混用无所谓的,AC就行。现在自己觉得,区分开来很重要!C++面向对象,拥有丰富的STL。C效率高,偏底层。该用什么用什么,用C++,就要用C++的风格,比如用迭代器替换i++之类的。
- Always ask WHY.学知识一定要学的明白,懂基本原理,使用这方面,倒是可以一点一点来熟练。基本不扎实,是我花了这么长时间调试的根本原因。string是个对象啊!那它存的就是个指针啊!二进制写进去这么个对象,怎么能类似”深拷贝“地把对象也写进去呢?从逻辑上想想,这真的是一个很白痴的错误。现在想想,是很简单,但是在学习的时候想到”这里保存的是指针“这一点会引发什么样的后果,却很难。需要认真思考。
很多东西,说说简单,不自己吃点亏,是记不住的。