最近弄数据仓库的元数据,这工作里面的一项重头戏就是解析SQL语句。由于数据仓库的数据加工逻辑比较复杂,成百上千行的SQL随处可见,因此如何把其中的数据来源与去向清晰的整理出来是非常重要的,以前我解析SQL语句用C++自己写,能实现部分功能,这次用的工具是Flex和Bison,学过编译原理的都知道大名鼎鼎的Lex/yacc这两个工具,Flex和Bison就是Lex/yacc的windows版本,Flex是解析词法的,Bison是用来解析语法的。
我写了一个Flex的例子测试,这个例子能把SQL语句群按照分号隔开,放入一个list,并且读出每一句SQL的起始和结束的位置,以及该SQL的类型,例如是一个空SQL还是只含注释的SQL,还是一个标准SQL。Flex这个工具生成读入后缀是l的词法文件,然后输出一个lex.yy.c的文件,我写了个程序测试这个lex.yy.c。我的目标是把这个解析器做成MFC DLL或者能输出xml的标准程序,这样以后的元数据项目就能直接用了,甚至能通过GUI界面处理SQL,但是Flex生成的.c文件MFC程序无法直接使用,首先要注释掉c文件中的#include <unistd.h>这一行,这行会报错,再修改b->yy_is_interactive = file ? (isatty( fileno(file) ) > 0) : 0;这一行,让b->yy_is_interactive = 0,然后在把.c文件的的开头的#include <stdio.h>替换成#include <stdafx.h>,这样就行了么?还不行,VC2003编译还是报错,我鼓捣半天才发现,需要把lex.yy.c文件重命名成lex.yy.cpp就可以了,这一系列操作太复杂了,我写了一个批处理文件生成cpp,然后又写了一个VC的宏来修改文件,这样,按两下鼠标一切就都OK了,嘿嘿,懒人就喜欢自动化。
然后写了简单的界面输入SQL进行解析,解析效果不错,但是运行后却发现了两处内存泄露,一处是16386字节,一处是40字节。在这里先给非程序员普及一下内存泄露的知识,任何一个计算机程序,在运行的时候存放数据是需要内存的,需要多少内存是程序向操作系统申请的,这块内存用完了就把它还给操作系统,操作系统可以再分配给其他程序。就像我们去饭馆吃饭,饭菜就是数据,内存就是碗和碟子,我们点了菜又点汤,这时候碗不够了,我们就会喊一声:“老板,再拿两个碗来盛汤”,这就是内存申请,等我们吃完了抹嘴买单走人,服务员收拾碟子和碗,这就是内存回收,如果我们看到这家饭馆的碗太漂亮了,于是偷偷拿走一个(这事我经常干),这就是内存泄露。如果偷碗的人太多了,这家饭馆的碗就不够了,你再申请要碗老板就会说碗不够了,请稍等一下,于是你们几个人只能用一个碗吃饭,吃的很慢,这就叫内存不足。C++和C的程序太灵活了,请求碗和送回碗都是程序员自己来做,就像一个饭馆没人看管,完全靠个人自觉性来维持,因此不管是水平差也好,疏忽也好,C/C++程序会很容易产生内存泄露,java和C#就好多了,他们就相当于饭馆门口有搜身的,你一个碗也带不走。
这里我发现了两处内存泄露,一共16KB左右,你可能会说才16KB,现在内存都好几个GB,这么点算什么,但是如果这是一个服务器上常年不停机运行的程序,有很多人来访问,会很快把内存吃掉的。虽然我这个程序不是服务器上运行的程序,但是我能容忍程序的bug,却不能容忍内存泄露,想当年我刚刚写C++程序的时候,程序有内存泄露,我死活找不出来是哪里的问题,最后只能告诉客户说我这个程序要求内存多,你的电脑需要增加内存,于是客户增加了内存,但即使这样也不行,还需要半夜重启一下机器才可以。在此我对该客户表示深深的歉意,从此我发誓,再也不让我的程序有一个字节的内存泄露,于是深山苦练coding和调试技术,经过多年的浸淫,自己写的代码肯定不会有这样的错误了,而且别人的多复杂的问题代码我拿过来就调试,就跟饭端过来就吃一样easy。
这回的问题我认为很easy,调试呗,一开始以为是list有问题,这是很容易出问题的地方,CList是一个模板类,用了好多年了,好用量又足,我们一直用它,但是CList里面如果放入指针的话就要注意了,简单的Removeall是不行的,还需要一个一个的delete掉里面的对象指针,我跟踪了一遍,不是它的问题,每次内存泄露的大小都是那么多,与list的大小没关系。难道是我写的CSQLSet和CSQLNode这两个类有问题?仔细查了一遍也没问题,奇了怪了,难道是lex.yy.c不能和MFC混在一起用,我有把这个程序拆出来,用纯c做了一遍,果然没报告内存泄露,好像是问题解决了。但是我如果简单的在程序里面用MFC CString类,就会报告泄露,CString这更是久经考验的共产主义战士,不可能有问题的,太令人困惑了,后来通过艰苦的内存检查发现,其实纯c的程序也有内存泄露!只不过VC2003没有报告罢了,这简直是VC的一个大bug!这太让我失望了,以前用VC6我比较喜欢numega的调试插件,它能发现比较隐秘的bug和泄露,但是VC2003我觉得应该不错了,就没去找这样的插件,没想到啊没想到,微软还是忽悠了我一下。
现在问题就集中在Flex生成的lex.yy.c上了,这个程序很长,好几千行,而且作者肯定是C的高手,很多地方没看懂,太牛了,我从头到尾大概浏览一遍,里面好几处申请了内存,可能就是它们的问题,但是这程序太复杂了无法下手啊,郁闷之中上网google,输入Flex memory leak,结果发现了Adobe有一个产品也叫Flex,而且也有内存泄露问题,我倒,什么世道啊,我又加入关键字lex.yy.c,这回搜出来的对了,原来不止一个人发现了这个问题,很多人都在报告这个问题,但是讨论都没结果,找到Flex的老家sourceforge.net,打算投诉一下作者,看到上面有讨论,又搜索了一下,作者针对内存泄露的问题说了,对于制作解析非C的解析器来说,可能会有泄露问题,解决的方案是在你真的准备结束解析的时候加上这两句代码:
yy_delete_buffer(YY_CURRENT_BUFFER);
yy_init = 1;
我加上了,好了,困扰我两天的问题解决了,这下世界清静了。但是看到其他的帖子说Bison也会有内存泄露,前面的路还很长。