最近弄數(shù)據(jù)倉(cāng)庫(kù)的元數(shù)據(jù),這工作里面的一項(xiàng)重頭戲就是解析SQL語(yǔ)句。由于數(shù)據(jù)倉(cāng)庫(kù)的數(shù)據(jù)加工邏輯比較復(fù)雜,成百上千行的SQL隨處可見(jiàn),因此如何把其中的數(shù)據(jù)來(lái)源與去向清晰的整理出來(lái)是非常重要的,以前我解析SQL語(yǔ)句用C++自己寫(xiě),能實(shí)現(xiàn)部分功能,這次用的工具是Flex和Bison,學(xué)過(guò)編譯原理的都知道大名鼎鼎的Lex/yacc這兩個(gè)工具,F(xiàn)lex和Bison就是Lex/yacc的windows版本,F(xiàn)lex是解析詞法的,Bison是用來(lái)解析語(yǔ)法的。
我寫(xiě)了一個(gè)Flex的例子測(cè)試,這個(gè)例子能把SQL語(yǔ)句群按照分號(hào)隔開(kāi),放入一個(gè)list,并且讀出每一句SQL的起始和結(jié)束的位置,以及該SQL的類型,例如是一個(gè)空SQL還是只含注釋的SQL,還是一個(gè)標(biāo)準(zhǔn)SQL。Flex這個(gè)工具生成讀入后綴是l的詞法文件,然后輸出一個(gè)lex.yy.c的文件,我寫(xiě)了個(gè)程序測(cè)試這個(gè)lex.yy.c。我的目標(biāo)是把這個(gè)解析器做成MFC DLL或者能輸出xml的標(biāo)準(zhǔn)程序,這樣以后的元數(shù)據(jù)項(xiàng)目就能直接用了,甚至能通過(guò)GUI界面處理SQL,但是Flex生成的.c文件MFC程序無(wú)法直接使用,首先要注釋掉c文件中的#include <unistd.h>這一行,這行會(huì)報(bào)錯(cuò),再修改b->yy_is_interactive = file ? (isatty( fileno(file) ) > 0) : 0;這一行,讓b->yy_is_interactive = 0,然后在把.c文件的的開(kāi)頭的#include <stdio.h>替換成#include <stdafx.h>,這樣就行了么?還不行,VC2003編譯還是報(bào)錯(cuò),我鼓搗半天才發(fā)現(xiàn),需要把lex.yy.c文件重命名成lex.yy.cpp就可以了,這一系列操作太復(fù)雜了,我寫(xiě)了一個(gè)批處理文件生成cpp,然后又寫(xiě)了一個(gè)VC的宏來(lái)修改文件,這樣,按兩下鼠標(biāo)一切就都OK了,嘿嘿,懶人就喜歡自動(dòng)化。
然后寫(xiě)了簡(jiǎn)單的界面輸入SQL進(jìn)行解析,解析效果不錯(cuò),但是運(yùn)行后卻發(fā)現(xiàn)了兩處內(nèi)存泄露,一處是16386字節(jié),一處是40字節(jié)。在這里先給非程序員普及一下內(nèi)存泄露的知識(shí),任何一個(gè)計(jì)算機(jī)程序,在運(yùn)行的時(shí)候存放數(shù)據(jù)是需要內(nèi)存的,需要多少內(nèi)存是程序向操作系統(tǒng)申請(qǐng)的,這塊內(nèi)存用完了就把它還給操作系統(tǒng),操作系統(tǒng)可以再分配給其他程序。就像我們?nèi)ワ堭^吃飯,飯菜就是數(shù)據(jù),內(nèi)存就是碗和碟子,我們點(diǎn)了菜又點(diǎn)湯,這時(shí)候碗不夠了,我們就會(huì)喊一聲:“老板,再拿兩個(gè)碗來(lái)盛湯”,這就是內(nèi)存申請(qǐng),等我們吃完了抹嘴買(mǎi)單走人,服務(wù)員收拾碟子和碗,這就是內(nèi)存回收,如果我們看到這家飯館的碗太漂亮了,于是偷偷拿走一個(gè)(這事我經(jīng)常干),這就是內(nèi)存泄露。如果偷碗的人太多了,這家飯館的碗就不夠了,你再申請(qǐng)要碗老板就會(huì)說(shuō)碗不夠了,請(qǐng)稍等一下,于是你們幾個(gè)人只能用一個(gè)碗吃飯,吃的很慢,這就叫內(nèi)存不足。C++和C的程序太靈活了,請(qǐng)求碗和送回碗都是程序員自己來(lái)做,就像一個(gè)飯館沒(méi)人看管,完全靠個(gè)人自覺(jué)性來(lái)維持,因此不管是水平差也好,疏忽也好,C/C++程序會(huì)很容易產(chǎn)生內(nèi)存泄露,java和C#就好多了,他們就相當(dāng)于飯館門(mén)口有搜身的,你一個(gè)碗也帶不走。
這里我發(fā)現(xiàn)了兩處內(nèi)存泄露,一共16KB左右,你可能會(huì)說(shuō)才16KB,現(xiàn)在內(nèi)存都好幾個(gè)GB,這么點(diǎn)算什么,但是如果這是一個(gè)服務(wù)器上常年不停機(jī)運(yùn)行的程序,有很多人來(lái)訪問(wèn),會(huì)很快把內(nèi)存吃掉的。雖然我這個(gè)程序不是服務(wù)器上運(yùn)行的程序,但是我能容忍程序的bug,卻不能容忍內(nèi)存泄露,想當(dāng)年我剛剛寫(xiě)C++程序的時(shí)候,程序有內(nèi)存泄露,我死活找不出來(lái)是哪里的問(wèn)題,最后只能告訴客戶說(shuō)我這個(gè)程序要求內(nèi)存多,你的電腦需要增加內(nèi)存,于是客戶增加了內(nèi)存,但即使這樣也不行,還需要半夜重啟一下機(jī)器才可以。在此我對(duì)該客戶表示深深的歉意,從此我發(fā)誓,再也不讓我的程序有一個(gè)字節(jié)的內(nèi)存泄露,于是深山苦練coding和調(diào)試技術(shù),經(jīng)過(guò)多年的浸淫,自己寫(xiě)的代碼肯定不會(huì)有這樣的錯(cuò)誤了,而且別人的多復(fù)雜的問(wèn)題代碼我拿過(guò)來(lái)就調(diào)試,就跟飯端過(guò)來(lái)就吃一樣easy。
這回的問(wèn)題我認(rèn)為很easy,調(diào)試唄,一開(kāi)始以為是list有問(wèn)題,這是很容易出問(wèn)題的地方,CList是一個(gè)模板類,用了好多年了,好用量又足,我們一直用它,但是CList里面如果放入指針的話就要注意了,簡(jiǎn)單的Removeall是不行的,還需要一個(gè)一個(gè)的delete掉里面的對(duì)象指針,我跟蹤了一遍,不是它的問(wèn)題,每次內(nèi)存泄露的大小都是那么多,與list的大小沒(méi)關(guān)系。難道是我寫(xiě)的CSQLSet和CSQLNode這兩個(gè)類有問(wèn)題?仔細(xì)查了一遍也沒(méi)問(wèn)題,奇了怪了,難道是lex.yy.c不能和MFC混在一起用,我有把這個(gè)程序拆出來(lái),用純c做了一遍,果然沒(méi)報(bào)告內(nèi)存泄露,好像是問(wèn)題解決了。但是我如果簡(jiǎn)單的在程序里面用MFC CString類,就會(huì)報(bào)告泄露,CString這更是久經(jīng)考驗(yàn)的共產(chǎn)主義戰(zhàn)士,不可能有問(wèn)題的,太令人困惑了,后來(lái)通過(guò)艱苦的內(nèi)存檢查發(fā)現(xiàn),其實(shí)純c的程序也有內(nèi)存泄露!只不過(guò)VC2003沒(méi)有報(bào)告罷了,這簡(jiǎn)直是VC的一個(gè)大bug!這太讓我失望了,以前用VC6我比較喜歡numega的調(diào)試插件,它能發(fā)現(xiàn)比較隱秘的bug和泄露,但是VC2003我覺(jué)得應(yīng)該不錯(cuò)了,就沒(méi)去找這樣的插件,沒(méi)想到啊沒(méi)想到,微軟還是忽悠了我一下。
現(xiàn)在問(wèn)題就集中在Flex生成的lex.yy.c上了,這個(gè)程序很長(zhǎng),好幾千行,而且作者肯定是C的高手,很多地方?jīng)]看懂,太牛了,我從頭到尾大概瀏覽一遍,里面好幾處申請(qǐng)了內(nèi)存,可能就是它們的問(wèn)題,但是這程序太復(fù)雜了無(wú)法下手啊,郁悶之中上網(wǎng)google,輸入Flex memory leak,結(jié)果發(fā)現(xiàn)了Adobe有一個(gè)產(chǎn)品也叫Flex,而且也有內(nèi)存泄露問(wèn)題,我倒,什么世道啊,我又加入關(guān)鍵字lex.yy.c,這回搜出來(lái)的對(duì)了,原來(lái)不止一個(gè)人發(fā)現(xiàn)了這個(gè)問(wèn)題,很多人都在報(bào)告這個(gè)問(wèn)題,但是討論都沒(méi)結(jié)果,找到Flex的老家sourceforge.net,打算投訴一下作者,看到上面有討論,又搜索了一下,作者針對(duì)內(nèi)存泄露的問(wèn)題說(shuō)了,對(duì)于制作解析非C的解析器來(lái)說(shuō),可能會(huì)有泄露問(wèn)題,解決的方案是在你真的準(zhǔn)備結(jié)束解析的時(shí)候加上這兩句代碼:
yy_delete_buffer(YY_CURRENT_BUFFER);
yy_init = 1;
我加上了,好了,困擾我兩天的問(wèn)題解決了,這下世界清靜了。但是看到其他的帖子說(shuō)Bison也會(huì)有內(nèi)存泄露,前面的路還很長(zhǎng)。