背景
人生第一次读项目源码。
这是SIM查重工具,由大牛Dick Grune创造。文档和源码都可以在他的网站里找到。Paper是.ps文件,需要下载Adobe Acrobat打开阅读,也可以用Acrobat把它导出成pdf,用习惯的pdf阅读器看。
关于怎么运行他的源码,我的上一篇博客记录了配置MinGW的过程。当然,只是使用的话,直接下exe包就行了。
前段时间云里雾里地读了一下SIM提供的三个文档,Manual,Paper,Technique Report,然后这几天一头扎进源码里面,希望细节性地了解了它的实现以后,试图做一点创新,以此作为我大创项目的主要工作(这都三月份了,大创四月份就要结题了,说实话慌得很)。
第一次读源码,没有经验,就一头扎到main里面一层层地读下去了,过程中有一些心得随缘地记录一下。
前言
光对着源码看几秒钟就走神,所以一边写一边看来让自己注意力集中,这就是一篇笔记,质量就不强求了。
因为是一头扎到main()里面去看的,所以就从main讲起吧。
初看sim.c
main()在sim.c里面,所以我就从sim.c开始看起了。有一个感受,就是开始写这篇博客的时候看过的函数已经遍布五六七八个文件了,但是每个文件的代码量其实都在差不多的范围里,差不多都是200到400行的样子,这里面是有不少封装的思想。
oplist[]
这个文件里main()之前的内容并不多,最显眼的还是那一长串的oplist,这个是选项列表,因为sim工具给用户提供了许多的设置选项,可以根据不同需要进行查重,比如哪些文件不需要相互进行比较(old文件)、要不要按百分比显示查重率、百分之多少以上的重复率输出等等。
这个指令列表预设了这些指令,它是stuct option*类型,关于这个结构体,需要注意的就是里面有一个enum类型的变量,它指示的是这个指令后面所跟的参数,比如-r后面需要跟一个整型参数,表示MinRunSize被调整成多少。
读入参数指令
在Read_Input_Files(argc, argv)被调用之前,其实都是一些准备的工作,比如把版本号设置出来,保留progname,读入从命令行得到的参数,调用do_options,把这些参数都存到options列表当中去,用于后面检查哪些参数是被设定的。可以说主要就是对输入的参数指令进行检查,再做一些处理,比如如果接收到-v这个参数,就可以直接输出版本号结束程序了。
这里面应该说,感受到真正的项目和我之前做的算法题,还有我做的一些大作业比起来,它的出错处理就做得很精细了,在这里的准备工作里面,就对各种命令之间的冲突做了判断,如果有所错误,会进行相应的报错,然后退出。
读入文件
主要是Read_Input_Files的调用。
其实就是这一块略略读完之后,激发了我写博客的动力。之前也就碰到个结构体,是option,到这里它开始了层层调用,并且出现了text这个结构体,我怕不写一下博客,过几天回来看就忘了这一块在干什么了。
/*这是text类型的定义*/
struct text {
const char *tx_fname; /* the file name */
size_t tx_start; /* index of first token in Token_Array[]
belonging to the text */
size_t tx_limit; /* index of first position in Token_Array[]
not belonging to the text */
/* 就表示这一段在Token_Array里面是[tx_start, tx_limit) */
int tx_EOL_terminated; /* Boolean */ /*暂时不知道干嘛的*/
struct position *tx_pos;/* list of positions in this file that are
part of a chunk; sorted and updated by
Pass 2
*/
};
struct position {
/* position of first and last token of a chunk */
struct position *ps_next;
int ps_type; /* first = 0, last = 1, for debugging */
size_t ps_tk_cnt; /* in tokens; set by add_run()
in Read_Input_Files() */ /*最后用来输出匹配数量*/
size_t ps_nl_cnt; /* same, in line numbers;set by Retrieve_Runs(),
used by Print_Runs(), to report line numbers
*/ /* 最后用来输出匹配行号 */
};
其实感觉text已经像是被封装成一个类了,不过只是封装的感受强烈,OOP当中多态和继承的思想它没有用到(也不需要吧)。目前对text的理解,就是它其实是指示了,在Token_Array当中,当前这个文件从第几号Token开始,从第几号Token结果。position的属性要等到pass2再来详看作用了。
Read_Input_File的调用一层又一层,尤其是“打开文件”这一个功能,后面还有流式文件的封装,给我绕晕了。琢磨了一段时间发现没啥效果,我就“抓大放小”地把后面几层调用先给放了。总的来说,这个模块就是把打开的文件中的token识别出来,存放在Token_Array数组当中。追溯获得token的方法,就是通过flex的yylex()来获得。词法分析和token的存储还蛮重要的,第一遍看没有能特别看得细,后面可能需要回来细看。
Compare_Files()
main()当中对这个函数的注释是"turns texts into runs",我在看文档的时候就对run这个单位有些迷惑,这部分明显是真正开始核心工作的地方。
这个函数又由三个函数嵌套形成:
void
Compare_Files(void) {
Make_Forward_References();
compare_texts();
Free_Forward_References();
}
Make_Forward_References()
从TechnReport里大概了解到Foward_References,它是加速匹配的一种方法,但是看的时候对使用last_index[]对Foward_References进行更新的过程有所迷惑。这里方便起见,翻译一下TechnReport,也是注释里,对这个过程的描述。
文件的比较方法是,每个子串都和它所有右边的子串进行匹配,这个过程实际上是平方的复杂度,但是由于我们只会去关注超过'Min_Run_Size'长度字串的匹配结果,这就是我们有可能使用哈希表进行优化。
对文本中的每个位置p,我们构造一个下标数组