最近弄數據倉庫的元數據,這工作里面的一項重頭戲就是解析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也會有內存泄露,前面的路還很長。