• <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>

            loop_in_codes

            低調(diào)做技術(shù)__歡迎移步我的獨立博客 codemaro.com 微博 kevinlynx

            #

            實現(xiàn)一種解釋性腳本語言(三)

            author: Kevin Lynx email: zmhn320#163.com date: 3.7.2009

            詞法分析

                詞法分析屬于整個編譯流程中的第一個階段。為什么要把編譯過程分為多個階段,這就
            如同軟件分層一樣,個人覺得是出于降低復(fù)雜性的考慮。
                再次聲明我不會告訴你任何編譯原理的理論知識,因為坦率地說我也不會:D。所以我努
            力將我們需要了解的概念盡可能簡單地告訴你。當(dāng)然,可能會與教科書不吻合。

            什么是詞法分析?

                詞法分析就是把一段話整理成單詞集合。舉個簡單的例子,例如有代碼:age = age + 1;,
            經(jīng)過詞法分析后,將得到:age、=、age、+、1、;幾個符號。為了方便,我稱每個單詞為一
            個token。

            詞法分析的作用

                詞法分析分析出來的單詞集合,直接作為編譯流程中接下來的語法分析的輸入。那么語
            法分析階段面對的將是一個一個的token,而不是單個的字符。
                例如,在處理age = age + 1;這種語句時,當(dāng)我們獲取到token "="時,我們直接期望接
            下來的token應(yīng)該是個表達(dá)式。以單詞為單位地處理,比直接處理單個字符簡單很多。

            詞法分析的過程

                詞法分析的輸入是單個字符流,一般我們fopen一個源代碼文件,保存在一個char緩存
            里,這就是詞法分析的輸入。而詞法分析的最終輸出結(jié)果就是一個一個的token。
                為了處理方便,token并不是單純的單詞。通常我們會將源代碼中的所有單詞分類,例
            如變量名其實都屬于一類token。簡單的token可定義為:
                struct Token
                {
                    int type;
                    char value[256];
                };
                type用于表示token的類型,例如一個變量名token的類型是一個標(biāo)識符。value可以用
            來具體地保存這個變量的名字。

                對于type的處理,通常會事先定義一組枚舉值,例如:
                enum    {    ID, NUM, STRING, IF, ELSE, WHILE, RETURN, FUNCTION }等等用于標(biāo)示
            在一個源代碼中可能出現(xiàn)的所有token。

                雖然說詞法分析的結(jié)果是一個token集合,但事實上我們并不是一次做完詞法分析。通常
            詞法分析模塊提供一個get_token函數(shù)。每次調(diào)用該函數(shù)時,都返回源代碼中下一個token。
            例如,有源代碼:age = age + 1;
                第一次調(diào)用get_token將獲得 { ID, "age" },第二次獲得 { ASSIGN, "=" },第三次
            獲得{ ID, "age" },等等。

                那么,詞法分析該如何實現(xiàn)?也就是struct Token get_token()函數(shù)如何實現(xiàn)?其實很
            簡單,你告訴我:給你一個字符串,你如何判斷這個字符串全部是數(shù)字?
                int is_num( const char *str )
                {
                    while( *str != 0 )
                    {
                        if( !isdigit( *str++ ) ) return 0;
                    }
                    return 1;
                }
                所以,基本上,詞法分析的過程也就是這個過程。就拿標(biāo)識符舉例,典型的標(biāo)識符一般
            以字符開頭,然后接著是數(shù)字或字符或_,當(dāng)遇到非法字符時,這個標(biāo)識符的掃描即結(jié)束。
                詞法分析一般是個while+switch:
                struct Token get_token()
                {
                    while( current_char != 0 )
                    {
                        switch( current_char )
                        {
                            case CHARACTER:
                                /* 掃描一個標(biāo)識符 token */
                                break;

                            case '=':
                                /* 獲得一個 ASSIGN token */
                                break;

                                ...
                        }
                    }
                }

                現(xiàn)在,試著去總結(jié)一門語言里的每一個token的規(guī)則,然后自己去寫寫看。

            代碼導(dǎo)讀

                在本節(jié)我將提供kl在googlecode的SVN上的代碼,先不要去管代碼包中的其他東西。關(guān)于
            詞法的代碼可以在kllex.c kllex.h中找到。lex_token是提供給其他模塊的接口,用于獲取
            當(dāng)前掃描的token。掃描結(jié)果可以通過lexState結(jié)構(gòu)體獲取。
                再次提下版權(quán)問題,代碼文件以及代碼包中我并沒有加入任何版權(quán)說明,哪怕是GPL。
            但是如同我之前說的一樣,我不介意你傳播、改動此代碼,但是請保留原作者信息。當(dāng)然,
            我并不介意你加上@modified by xxx:)。

                下載kl源代碼:http://klcommon.googlecode.com/files/kllan_0.1.0.zip

            posted @ 2009-03-07 13:43 Kevin Lynx 閱讀(3831) | 評論 (2)編輯 收藏

            實現(xiàn)一種解釋性腳本語言(二)

            author: Kevin Lynx email: zmhn320#163.com date: 3.6.2009

            語言特性

                在正式討論實現(xiàn)細(xì)節(jié)前明確下這個腳本語言的一些語言特性,基本上可以讓我們預(yù)見將
            來會遇到哪些難題。總的來說,它(腳本)將同我們平時接觸的如lua一樣的腳本語言:擁
            有一般的編程語言特性,如變量、各種控制流程、也許還有函數(shù),另一方面它還應(yīng)該和它的
            宿主語言結(jié)合,如作為一個庫被用進(jìn)C,這還涉及到給這門語言設(shè)計一種插件方式,最好能
            通過獨立的解釋程序讓腳本載入一些插件運行。

                以下在描述我寫的這個腳本語言時,將以kl表示它的名字,以方便描述。

            代碼塊:

                首先從整體風(fēng)格上,kl如同C語言一樣被劃分為函數(shù)塊,如:
                function func1()
                {
                }
                function func2()
                {
                }
                ...
                kl支持以{}隔離代碼塊,但是這并不意味著kl有多個獨立的局部堆棧,如同C語言一樣。
            這些細(xì)節(jié)暫不討論。本節(jié)描述的所有內(nèi)容你都不必深究,因為我只要求你對kl有個感性上的
            認(rèn)識。
                函數(shù)塊之外沒有可執(zhí)行的語句(statement)。那么你可能會想到程序的入口點也許會是
            main。事實上從kl提供的庫來看,并沒有這種硬性要求。但是,kl的獨立解釋程序是這樣要
            求的。   

            變量:

                kl允許你在任何地方使用一個變量。變量不需要事先定義,任何地方出現(xiàn)一個合
            法的標(biāo)識符時,就意味著kl內(nèi)部會增加這個變量,并給予初值。變量也沒有靜態(tài)類型,也不
            會固定為某一類型。就一門最簡單的語言來看,我覺得數(shù)據(jù)類型無非就是字符串和數(shù)字類型

                所以,kl的變量在某一時刻必然是數(shù)字,或者字符串。在腳本里,你無法獲知一個變量
            的類型,事實上也沒這個必要。說變量擁有一個類型屬性,倒不如說值(value)有一種類型
            屬性。
                當(dāng)字符串值與數(shù)字值參與運算時,如1+"a",其運算結(jié)果將自動轉(zhuǎn)換為字符串,也就是
            "1a"。
                一個只有標(biāo)識符的語句(statement)通常意味著你想定義一個變量。這種無聊的手段通
            常被用于定義全局變量。

            運算符:

                kl支持一般的C語言風(fēng)格的算術(shù)、比較、邏輯運算符。例如加減乘除、大于小于、邏輯
            與邏輯或。

            作用域:

                kl腳本里只有兩個作用域:全局的和局部的。
                位于所有函數(shù)塊外的變量處于全局作用域;位于函數(shù)內(nèi)的變量處于局部作用域,位于函
            數(shù)塊內(nèi)的代碼塊變量,還是處于局部作用域。
                當(dāng)局部作用域內(nèi)出現(xiàn)一個全局里的同名變量時,優(yōu)先取局部作用域里的變量。這同C語
            言一樣。

            控制語句if:
                if的語法同C語言一樣,如:
                if( a > 10 )
                {
                }
                else
                {
                }
                if( a > 10 )中的a>10被我成為條件語句,所有條件語句,包括下面的while,都不能
            為字符串。例如if( "a" )將被視為非法語句。(我為什么要這樣考慮?- -!)

            控制語句while:

                c-like while:
                while( a > 10 )
                {
                }
                很遺憾,我暫時沒有加入對for的支持。因為我覺得既然有了while,有了循環(huán)控制,在
            沒有更多無聊時間的前提下,我沒有必要加入for。

            函數(shù):

                很遺憾,函數(shù)的定義和調(diào)用和C語言有點不一樣。這是因為kl沒有變量類型,那就意味
            著函數(shù)定義如果和C語言一樣,就會出現(xiàn)語法歧義,如:
                func( a )
                {
                }
                就會和函數(shù)調(diào)用func(a)出現(xiàn)混淆。所以,我加入了function關(guān)鍵字。定義函數(shù)的語法
            為:
                function func( a, b )
                {
                }
                如你所見,函數(shù)支持參數(shù)傳遞,當(dāng)然也支持return a;返回值。kl是簡陋的,因為它沒
            有指針之類的概念,所以你無法為函數(shù)傳遞一塊數(shù)據(jù)。當(dāng)然,kl也不能像lua一樣讓函數(shù)可
            以返回多個值。
                函數(shù)調(diào)用的語法相對熟悉:
                func( 1, 3 );

            數(shù)組:

                從一開始我就沒考慮為kl加入數(shù)組。事實證明加入數(shù)組是一個不明智的做法。數(shù)組的支
            持讓代碼在很多地方變得臟亂。無論如何,kl后來支持一維數(shù)組了。為了讓代碼保持那么一
            點點的干凈,我甚至為定義數(shù)組加入dim的關(guān)鍵字。這意味著,在kl里,數(shù)組和一般的變量
            總有點不一樣:變量無需定義,數(shù)組卻必須事先定義。
                數(shù)組的長度不支持動態(tài)擴充。如果支持,我得讓kl內(nèi)部更好地去管理內(nèi)存。
                數(shù)組元素的類型沒有硬性的規(guī)定,這意味著a[0] = 1; a[1] = "a";是允許的。

                語言特性上就描述這些,在本節(jié)末尾我決定貼一段kl計算階乘的代碼:

            /* fac.kl */
            function main()
            {
                n = input( "%d" );
                print( "fac(" + n + ") = " + fac( n ) );
            }

            function fac( n )
            {
                if( n == 1 )
                {
                    return 1;
                }
                else
                {
                    return fac( n - 1 ) * n;
                }
            }

            posted @ 2009-03-06 16:01 Kevin Lynx 閱讀(4726) | 評論 (9)編輯 收藏

            實現(xiàn)一種解釋性腳本語言(一)

            author: Kevin Lynx email: zmhn320#163.com date: 3.6.2009

                (相信我,這一節(jié)全是廢話。)
                我不是標(biāo)題黨,但是有必要解釋下這個標(biāo)題。綜合來說我就是想與你分享我所學(xué)到的。
            我會將我實現(xiàn)的這個簡單的腳本語言的實現(xiàn)細(xì)節(jié)展示給你。它將涵蓋:詞法分析、語法分析
            、符號表管理、語法樹解釋執(zhí)行、插件管理等內(nèi)容。
                我并不擅長傳授編譯原理知識。我沒有聽過編譯原理課,所以我也不會編譯原理(也許
            即使我聽了也不會:D)。所以對于這方面的能手而言,我口中的‘DFA‘可能會貽笑大方。
                顯然,CPPBLOG上有編譯原理上的大牛。如果你想學(xué)習(xí)更深入的知識,可以去請教他們。
            vczh(http://www.shnenglu.com/vczh/) 看起來是我所說的這個人。在致謝名單里我將真誠地
            寫上他的名字。他的’手把手xxx腳本‘系列多多少少還是給了我一些有用的信息。
                其次是FOX,在詞法分析的DFA和NFA那里我請教了他一些問題。雖然我現(xiàn)在又忘了。如
            你們所知,理論和實現(xiàn)之間總會隔著鴻溝。

                推薦《編譯原理與實踐》(<Compiler Construction:Principles and Practice>
            Kenneth C. Louden)這本書。在你將來閱讀我的腳本語言的實現(xiàn)代碼時,你會發(fā)現(xiàn)有很一些地
            方同這本書里的TINY語言實現(xiàn)代碼有相似之處。建議你閱讀TINY的代碼。
                感謝VIM、GCC、GDB、MingW,我用這些軟件在工作之余寫出了這個東西的幾千行C代碼。
            很明顯我是個開源文化的愛好者。但是我不會告訴你unix有多么多么好,因為我也是個初學(xué)
            者,我還不懂unix。開源在我看來更是一種分享知識的精神。讓這種精神如同GPL一樣病毒
            式地傳染下去。
                還有版權(quán)問題。但也許它不是個問題。我不會添加任何版權(quán)信息。我允許你任意傳播、
            改動我所散播的東西,但是唯一的基本條件是:保留作者的信息---不要告訴別人,這東西
            是你做的。

                在所有的文章發(fā)布后,我都可能會再次修改。也許通過RSS或者日志日期之類你可以獲
            得修改提醒。

            posted @ 2009-03-06 15:58 Kevin Lynx 閱讀(6530) | 評論 (4)編輯 收藏

            玩了一下alienbrain的EventsScript

            從我接觸的UNIX文化中我知道,要學(xué)會更好地去使用工具去組合工具來完成日常的工作。項目使用alienbrain作為源代碼管理工具。每次在VC里有些代碼文件里總會寫些并不想簽入到代碼服務(wù)器上的代碼,例如我經(jīng)常#include "vld.h"來檢測內(nèi)存泄漏。但是真的等到簽入代碼時,又經(jīng)常忘記刪除這些代碼。

             

            于是我想,如果每次在我簽入代碼前,VC或者alienbrain或者其他工具能根據(jù)我的設(shè)置檢查我的代碼中是否存在這種不想簽入的代碼,然后提示我。問了一下向來很熟悉工具的FOX(很抱歉,我甚至不會使用WORD,我真不敢相信我的坦率:D),結(jié)果他也不知道。今天閑來無事翻了下alienbrain附帶的文檔,發(fā)現(xiàn)EventsScript。

             

            alienbrain里的EventScript總的來說是一種讓用戶定制更多功能的機制。它定義諸如簽入簽出獲取版本等動作為event,然后用戶可以使用alienbrain支持的VBScript和JavaScript腳本來處理這些事件。例如,當(dāng)你簽出一個文件時,ab(alienbrain)會調(diào)用你的腳本,讓你來處理這一事件)。當(dāng)腳本返回時,ab繼續(xù)處理剛才的操作(如果你實在想中止這一動作也可以)。

             

            這正是我需要的一種機制。迅速地看完了EventScript這一節(jié)的文檔。再想想我的需求,大體思路就是:給check in掛個腳本,讓該腳本調(diào)用一個外部程序,處理將要check in的文件,分析這個文件。我可以規(guī)定在我的代碼中凡是不想簽入的代碼都可以附加一個tag,例如:#include "vld.h" //DUMMY,分析該代碼文件時,發(fā)現(xiàn)代碼中有//DUMMY字符串,就提示我說該代碼文件有不想被簽入的代碼,位于某某行。

             

            看來我似乎需要一個grep工具,萬惡的是windows只有find。不過find基本可以滿足我的需求,但是find的輸出結(jié)果看起來并不是那么友好,并不是可以被另一個程序處理的格式。(linux下通過建立標(biāo)準(zhǔn)的輸入輸出格式,從而可以輕松地讓多個程序協(xié)作起來更好地完成很多事)無論如何,我需要使用ab提供給我的接口來試驗下我的想法。

             

            然后噩夢開始了。先是發(fā)現(xiàn)NxNLauncher(ab提供的一個接口,大致是這樣拼寫的,現(xiàn)在機器上沒ab)的Exec接口是異步執(zhí)行進(jìn)程,從而StdOut屬性及ExitCode總是得不到正確的值。后來一不小心在ab的wiki上看到,說Exec有個隱藏參數(shù),用來標(biāo)識是同步執(zhí)行進(jìn)程還是異步執(zhí)行,顯然,這個隱藏參數(shù)undocumented。- -!

             

            再然后是每次執(zhí)行find后,find程序倒是被執(zhí)行起來了,結(jié)果卻崩潰了。萬惡的是我執(zhí)行其他程序卻很正常。google了一下居然發(fā)現(xiàn)沒人遇到這個問題。最近我的windows總是崩潰各種程序,visual stdio, word, find。但是,我并不打算自己寫個find,即使是簡化版的。于是我決定寫個findwrapper,讓ab來調(diào)用我自己寫的程序,然后我這個程序調(diào)用find。然后為了搞定兩個程序的通信,又想起了管道pipe。當(dāng)然,這里的通信很簡單,我只需要讓find的輸出重定向到我的findwrapper,然后我的findwrapper又會被重定向到ab,然后我的ab腳本就可以捕獲這些輸出,然后再分析這些輸出就可以了。- -!

             

            其實,最簡單的解決方法,就是我自己寫個撇腳的程序,自己去分析要簽入的文件是否含有//DYMMY字符串。但是很顯然,我更喜歡證明我最初的想法是否正確。

             

            然后,在我的findwrapper里,再一次讓find崩潰。經(jīng)過幾次試驗,顯然問題出在共享管道上。find和ping的不同在于,find可以從標(biāo)準(zhǔn)輸入里獲取輸入,而ping只需要一個標(biāo)準(zhǔn)輸出(也許還有stderr)。在CreateProcess里傳入的startup info結(jié)構(gòu)體里,需要提供標(biāo)準(zhǔn)輸入、標(biāo)準(zhǔn)輸出、及錯誤輸出三個handle。顯然,問題出在標(biāo)準(zhǔn)輸入上,我提供了錯誤的值。按照一般的接口設(shè)計,我以為我如果不提供輸入句柄,find就會忽略。但是find顯然沒那么聰明,或者說,find更喜歡直接*ptr = 10而不是if( ptr != 0 ) *ptr = 10。無論如何,當(dāng)我把輸入句柄填為輸出句柄后,findwrapper可以捕獲find的輸出了,當(dāng)然,find也不崩潰了。

             

            我總覺得折騰windows api有點噩夢的感覺。我記得幾年前剛學(xué)windows編程的時候很多函數(shù)總要試驗很多次才能得到正確的結(jié)果,基本上我要嘗試各個參數(shù)的諸多組合。這還不包括上下文的影響,你要RegisterClass出了問題,CreateWindow楞是不會給你一點點有意義的error code,OMG。

             

             

             

            posted @ 2009-02-28 16:48 Kevin Lynx 閱讀(2693) | 評論 (3)編輯 收藏

            自己寫內(nèi)存泄露檢測庫

            author: kevin lynx

            這個內(nèi)存泄露工具最基本的原理就是利用宏替換掉標(biāo)準(zhǔn)的malloc、free(暫不考慮其他內(nèi)存分配函數(shù),
            如realloc、strdup),記錄下每次內(nèi)存分配和釋放動作。因為宏的處理發(fā)生在預(yù)處理階段,所以可以
            很容易地用你自己的malloc函數(shù)替換掉標(biāo)準(zhǔn)的malloc。例如:

            /* lib.h */
            #define malloc my_malloc
            #define free my_free 

            /* lib.c */
            /* disable these macro in this compile unit */
            #undef malloc
            #undef free 

            static int count = 0

            void *my_malloc( size_t size )
            {
                
            ++count;
                
            return malloc( size );
            }
             

            void my_free( void *a )
            {
                
            --count;
                free( a );
            }
             


            要使用以上代碼,用戶在使用時就需要包含lib.h,從而可以使用宏將用戶自己寫的malloc替換
            為my_mallo。當(dāng)程序退出時,如果count大于0,那么可以肯定的是有內(nèi)存泄露。當(dāng)然,如果
            count為負(fù)數(shù),則很可能對同一個指針進(jìn)行多次free。

            但是以上代碼的功能太局限了。一個真正的內(nèi)存泄露檢測庫(工具),至少需要報告泄露的代碼
            文件、函數(shù)、行數(shù)等信息。當(dāng)然,如果能報告調(diào)用堆棧,就更好了。不過這就依賴于具體的平臺,
            需要使用特定的系統(tǒng)接口才可以獲取出。

            要實現(xiàn)以上功能也很簡單,只需要在每次調(diào)用malloc的時候,通過編譯器預(yù)定義宏__FILE__、
            __LINE__、__FUNCTION__(__func__)就可以得到文件名、函數(shù)、行號等信息。將這些信息保存
            起來,然后在free的時候移除相應(yīng)的信息即可。

            最簡單的實現(xiàn)方式,就是保存一個表,表里記錄著每次分配內(nèi)存的信息:

             

            struct memRecord
            {
                
            char file[MAX_FILE_NAME];
                
            char func[MAX_FUNC_NAME];
                size_t lineno;
                
            void *address;
                size_t size;
            }


            struct memRecord mem_record[MAX_RECORD]; 

             

            但是,通過單單一個free函數(shù)的void*參數(shù),如何獲取出對應(yīng)的分配記錄呢?難道:

            for( size_t i = 0; i < MAX_RECORD; ++ i )
            {
                
            if( address == mem_record[i].address ) 
                
            {
                    
            /* shit */
                }

            }
             

             

            雖然可行,但是很stupid。這里提供一個小技巧:

             

            void *my_malloc( size_t size )
            {
                
            void *= malloc( size + sizeof( size_t ) );
                
            return (char*)a + sizeof( size_t );
            }
             

            void my_free( void *a )
            {
                
            /* actually, 'a' is not the real address */
                
            char *= ((char*)a - sizeof( size_t ) );    
                free( p );
            }

             

            意思就是說,我多分配了4字節(jié)內(nèi)存(sizeof( size_t ) ),用于保存這次分配記錄在mem_record
            中被保存的索引。在釋放內(nèi)存的時候,通過一些地址偏移計算,就可以獲取出真正的系統(tǒng)malloc
            返回的地址,從而安全釋放(別給我說這里的計算存在平臺和編譯器的限制,沒認(rèn)真看文章的SB才說)。

            另一個問題是,我們?nèi)绾翁幚砻看畏峙溽尫艜r,對于分配記錄那個數(shù)據(jù)結(jié)構(gòu),也就是mem_record。
            每一次free的時候,移除的記錄可能位于mem_record的任何位置。一定時間后,mem_record內(nèi)部
            將出現(xiàn)很多漏洞(已經(jīng)沒用的數(shù)組位置)。解決這個問題最直接當(dāng)然還是最stupid的方法,就是每次
            free移除記錄時重新整理一遍mem_record。如果你這樣做了,那你的malloc/free比微軟的還慢。

            我的解決方法是:
            size_t free_index[MAX_RECORD];
            用于保存這些出現(xiàn)漏洞的index。每一次free移除記錄時,就把這個記錄對應(yīng)的inex保存進(jìn)來,表示
            這個index指向的mem_record[]可用。每一次malloc的時候,先從這里取index,如果這里沒有,那
            可以直接從mem_record的末尾取。

            具體細(xì)節(jié)就不闡述了,典型的空間換時間方法。整個庫很簡單,代碼100來行。我也只進(jìn)行過粗略的
            測試。我肯定這100來行代碼是有問題的,相信自己的代碼存在問題是對bug的一種覺悟,哈哈哈。

            這個東西只檢測C語言的內(nèi)存泄露,其實要檢測C++的也很簡單,只需要重載new和delete就可以了。

            要放春節(jié)假了,在公司的最后幾個小時實在無聊,才做了這個東西,前后花了1個多小時,寫起來感覺
            不錯。

             

             

            代碼下載

             

            posted @ 2009-01-23 17:43 Kevin Lynx 閱讀(4394) | 評論 (5)編輯 收藏

            小寫了個XML解析器

                開始用FLEX做詞法分析,然后在此基礎(chǔ)上稍微做些符號匹配(實在稱不上語法分析),即完成了XML
            文件的簡單解析。
                我把XML文件拆分成:<, >, />, </, =, ID, STRING 等token。這樣一整理,用FLEX直接生成詞法
            分析程序。每一次getToken就返回這些token。上層的語法匹配就變得比較簡單。例如當(dāng)?shù)玫?/>"token
            時,我就可以判斷這是一個節(jié)點的結(jié)束;當(dāng)?shù)玫絀D token時,就可以推測下一個token為"=",再下一個
            是個STRING。不過對于部分token,也需要做一兩個token的回溯,例如當(dāng)遇到"<"時,并不一定表示一個
            新節(jié)點的開始,它可能是新節(jié)點的開始,同樣也可能是上一個節(jié)點的結(jié)束("</")。
                以我薄弱的編譯原理知識來看,解析XML變得非常容易。除此之外,還需要寫一些上層代碼來保存
            XML結(jié)構(gòu),以方面更上層代碼獲取XML文件的配置信息。因為我打算用純C來寫這個東西,所以數(shù)據(jù)結(jié)構(gòu)方
            面只有自己處理。這里我以一種變相的樹結(jié)構(gòu)來保存:每一個節(jié)點有兩個域:first child, sibling。
            其實這樣做是一個很明顯的通用做法,因為XML種每一個節(jié)點都可能擁有不定數(shù)量的children節(jié)點,如果
            讓parent直接去保存,顯然很笨。例如:
                <Resource>
                    <bmp file="1.bmp"/>
                    <bmp file="2.bmp"/>
                </Resource>
                可以使用這樣的數(shù)據(jù)結(jié)構(gòu)來存儲:
                struct xmlNode
                {
                    ...
                    struct xmlNode *child;
                    struct xmlNode *sibling;
                };
                對于Resource這個node而言,其child域指向第一個bmp節(jié)點(file屬性為1.bmp那個節(jié)點);對于第一
            個bmp節(jié)點而言,其sibling域則指向了第二個bmp節(jié)點。
                這個簡單的xml解析器是在公司外網(wǎng)機器上寫的,沒有VC,沒有任何IDE。代碼我是用VIM敲的,敲好
            后寫makefile,用mingw里的gcc、make來生成程序,用gdb來調(diào)試程序。這算是第一次離開VC寫的一個非
            練習(xí)程序(起碼用makefile來組織工程)。- -| makefile寫的比較爛,gdb用得很不熟,不過好歹調(diào)試出來
            了。越來越想換個平臺,只可惜工作還是得在windows vc下,很掃興。
                后來發(fā)覺詞法分析也很簡單,用FLEX的時候正則表達(dá)式都寫出來了。前段時間一直在看編譯原理,雖然不
            用功。但是就這里而言,基本可以直接根據(jù)正則表達(dá)式畫出DFA。終于不用接觸那惡心的從NFA轉(zhuǎn)DFA的
            過程,因為我至今不會,更不會寫代碼轉(zhuǎn)。- - 總而言之,自己手寫了詞法分析。邊寫邊參考編譯原理
            與實踐中附帶的tiny-c編譯器的詞法分析部分,最終發(fā)現(xiàn)我抄了一遍。MD,一點技術(shù)含量都沒有。

            附上全部源代碼(對于代碼我還是比較滿意的:D),下載

            posted @ 2008-12-10 16:22 Kevin Lynx 閱讀(4600) | 評論 (9)編輯 收藏

            最近的兩個問題:less for std::map,靜態(tài)變量初始化順序


            說下最近自己遇到的兩個值得讓人注意的問題。
            其一是關(guān)于自己給std::map寫less predicate,std::map第三個參數(shù)是一個典型的functor。map內(nèi)部將使用
            這個functor去判定兩個元素是否相等,默認(rèn)使用的是std::less。但是為什么傳入的是一個判斷第一個參數(shù)
            小于第二個參數(shù)的functor,而不是一個判斷兩個參數(shù)是否相等的functor?按照STL文檔的說法,當(dāng)檢查兩
            個參數(shù)沒有小于也沒有大于的關(guān)系時,就表示兩個參數(shù)相等。不管怎樣,我遇到了需要自己寫這個functor
            的需求,于是:

            struct MyLess
            {
                bool operator() ( long left, long right )
                {
                    //...
                }
            };

            DEBUG模式下編譯沒問題,RELEASE模式下卻出現(xiàn)C3848的錯誤。這就有點奇怪了,如果確實存在語法錯誤,
            那么DEBUG和RELASE應(yīng)該一樣才對。查了下MSDN,C3848的錯誤是因為const限定符造成的,如:

            const MyLess pr;
            pr(); // C3848

            于是,在operator()后加上const,就OK了。看了下VC std::map相關(guān)代碼,以為是DEBUG和RELEASE使用了不
            同的代碼造成。但是我始終沒找到不同點。另一方面,就STL內(nèi)部的風(fēng)格來看,應(yīng)該不會把predicator處理
            成const &之類的東西,全部是value形式的。奇怪了。

            第二個問題,涉及到靜態(tài)變量。這個東西給我的印象特別深刻,因為以前去一家外企應(yīng)聘時被問到,當(dāng)時
            覺得那個LEADER特別厲害。回來后讓我反思,是不是過多地關(guān)注了C++里的花哨,而漏掉了C里的樸素?導(dǎo)致
            我至今對純C存在偏好。

            說正題,我現(xiàn)在有如下的文件關(guān)系:

            // def.h
            struct Obj
            {
                Obj()
             {
              ObjMgr::AddObj( id, this );
             }
             int id;
            };

            struct ObjMgr
            {
                static void AddObj( int id, Obj *t )
             {
              ObjTable[id] = t;
             }
             static std::map<int, Obj*> ObjTable;
            };

            static Obj _t;

            // ObjMgr.cpp
            #include "def.h"

            static std::map<int, Obj*>::ObjMgr ObjTable;

            // main.cpp
            #include "def.h"

            這里舉的例子可能有點不恰當(dāng),我在一臺沒有編譯器的機器上寫的這篇文章。忽略掉這些旁支末節(jié)。我的意思,
            就是想讓Obj自己自動向ObjMgr里添加自己。我們都知道靜態(tài)變量將在程序啟動時被初始化,先于main執(zhí)行之前。

            上面代碼有兩個問題:

            一、
            代碼沒有按照我預(yù)期地執(zhí)行,如果你按照我的意思做個測試,你的程序甚至在進(jìn)main之前就crash了。我假定你
            用的是VC,因為我沒在其他編譯器上試驗過。問題就在于,Obj的構(gòu)造依賴于ObjTable這個map對象。在調(diào)試過程
            中我發(fā)現(xiàn),雖然ObjTable擁有了內(nèi)存空間,其this指針有效,但是,map對象并沒有得到構(gòu)造。我的意思是,Obj
            的構(gòu)造先于ObjTable構(gòu)造(下幾個斷點即可輕易發(fā)現(xiàn)),那么在執(zhí)行map::operator[]時,就出錯了,因為這個時候
            map里相關(guān)數(shù)據(jù)還沒準(zhǔn)備好。

            那是否存在某種機制可以手動靜態(tài)變量的初始化順序呢?不知道。我最后怎樣解決這個問題的?

            二、
            在還沒有想到解決辦法之前(不改變我的設(shè)計),我發(fā)現(xiàn)了這段代碼的另一個問題:我在頭文件里定義了靜態(tài)
            變量:static Obj _t; 這有什么問題?想想預(yù)編譯這個過程即可知道,頭文件在預(yù)編譯階段被文本展開到CPP
            文件里,然后,ObjMgr.cpp和main.cpp文件里都將出現(xiàn)static Obj _t;代碼。也就是說,ObjMgr.obj和main.obj
            里都有一個文件靜態(tài)變量_t。

            看來,在頭文件里放這個靜態(tài)變量是肯定不對的。于是,我將_t移動到ObjMgr.cpp里:

            // ObjMgr.cpp
            #include "def.h"

            static std::map<int, Obj*>::ObjMgr ObjTable;
            static Obj _t;

            按照這樣的順序定義后,_t的構(gòu)造居然晚于ObjTable了。也就是說,放置于前面的變量定義,就意味著它將被
            首先構(gòu)造初始化。這樣兩個問題都解決了。

            但是,誰能保證這一點特性?C標(biāo)準(zhǔn)文檔里?還是VC編譯器自己?

             

             

             


             

            posted @ 2008-11-11 17:55 Kevin Lynx 閱讀(7431) | 評論 (13)編輯 收藏

            讓人無語的boost

                關(guān)于BOOST,撞車,嚴(yán)重撞車。每一次都讓我有點無語。
                第一次是我所謂的宏遞歸,其實就是一個macro library,有一天就不小心在BOOST的library list上
            看到了這個東西。當(dāng)然,BOOST很牛,別人的這個macro是真的library。但是,我們的需求撞車,我們的
            實現(xiàn)手段撞車。于是下定決心下次想要實現(xiàn)個什么東西的時候,先去看看BOOST,可以省掉不少腦力。
                本來就沒有做好,何必吃力不討好?
                第二次,當(dāng)我寫下類似的模板代碼時:

                template <typename _Tp>
                
            void func( _Tp t );


                我總要花掉幾秒鐘時間去決策func的參數(shù)是用_Tp&還是_Tp,也就是所謂的究竟是按值傳送還是按引用
            傳送。如果按值傳送,當(dāng)_Tp為一個類時,復(fù)制的東西過多時,顯然效率上過不去。作為func的實現(xiàn)者,良
            心上更過不去。后來一想,STL的各種算法里到處都是按值傳送,這樣做總有它的理由吧?
                但是,這樣做就是不夠完美。
                于是想起了boost::ref。但是這個時候我并不知道boost::ref是個什么東西。我只是以前在各種地方
            看到過這個東西。我還是決定自己實現(xiàn)一個。
                實現(xiàn)一個什么?考慮有:

                template <typename _Tp>
                
            void func( _Tp t );


                而我這個時候要傳遞一個類對象過去CBaseObject obj。為了效率,我寫下如下的代碼:

                template <typename _Tp>
                
            class ref_wrapper
                
            {
                
            public:
                    ref_wrapper( _Tp 
            &p ) : _obj( &p ) { }
                    
            operator _Tp& () return *_obj; }    
                
            private:
                    _Tp 
            *_obj;
                }
            ;


                然后再使用func時,func( ref_wrapper<CBaseObject>( obj ) );這樣,發(fā)生復(fù)制操作的最多就是這
            個ref_wrapper,基本上也就是復(fù)制了一個指針,而不會復(fù)制整個obj。當(dāng)然,這里可以寫一個模板函數(shù)去
            簡化func的調(diào)用,如:

                template <typename _Tp>
                ref_wrapper
            <_Tp> ref( _Tp &t )
                
            {
                    
            return ref_wrapper<_Tp>( t );
                }


                這樣調(diào)用的時候就簡單了:func( ref( obj ) );
                其實這就是boost的ref庫,按照其官方文檔,ref庫就是:
                The Ref library is a small library that is useful for passing references to function
            templates (algorithms) that would usually take copies of their arguments.

                然后我就懵了。于是我不得不在kl_ref.h里寫上check out boost::ref for more information的字眼。

                好,接下來說說第三次。
                第三次我遇到了這樣一種需求,我需要一個容器,就像你知道的std::list。但是與std::list甚至STL
            中所有容器都不同的是,這個容器里保存的東西具有不同的類型。
                這個時候我想起了tuple。我沒有實現(xiàn)過tuple。大致上這個東西的實現(xiàn)原理就是利用類型遞歸來保存
            數(shù)據(jù),就像loki的type list。另一方面,tuple的尺寸似乎不能動態(tài)增長。
                于是我有了自己撇腳的實現(xiàn):

             

                class base_type
                
            {
                    
            virtual ~base_type() { }
                }
            ;
                template 
            <typename _Tp>
                
            class var_wrapper
                
            {
                
            public:
                    var_wrapper( 
            const _Tp &t ) : _t( t )  {}
                    
            operator _Tp& () return _t; }
                
            private:
                    _Tp _t;
                }
            ;   

                
            class var_list
                
            {
                
            public:
                    typedef std::vector
            <base_type*> TypeList;
                
            public:
                    
                    template 
            <typename _Tp>
                    
            void add( const _Tp &t )
                    
            {
                        var_wrapper
            <_Tp> *var = new var_wrapper<_Tp>( t );
                        _list.push_back( t );
                    }
             

                    template 
            <typename _Tp>
                    _Tp 
            &get( size_t index )
                    
            {
                        base_type 
            *base = _list.at( index );
                        typedef var_wrapper
            <_Tp> var_type;
                        var_type 
            *var = static_cast<var_type*>base );
                        
            return *var;
                    }

                
            private:
                    TypeList _list;
                }

             

                說白了,我就是利用一個包裝類將各種類型包裝其中,然后利用基類指針實現(xiàn)統(tǒng)一管理。直白地說,我
            對這個組件不滿意。讓人詬病的是,get接口是類型不安全的。例如:

                int a; 
                CBaseObject obj;
                var_list my_var_list;
                my_var_list.add
            <int>( a );
                my_var_list.add
            <CBaseObject>( obj );


                取出值的時候:

                int b = my_var_list.get<int>0 );
                CBaseObject cobj 
            = my_var_list.get<CBaseObject>1 );


                但是,因為get沒有類型檢查,即使你:

                CBaseObject cobj = my_var_list.get<CBaseObject>0 );


                也不會出錯,編譯器不會給予你警告。
                事情到此結(jié)束,這個類型不安全的組件只能依靠程序員自己的謹(jǐn)慎去生存。

                然后,又是一個不小心,我在boost里看到了any。boost::any庫同boost::ref庫一樣,是一個tiny
            library。幾十行的代碼一目了然。
                boost::any有一個placeholder基類,有一個template <typename ValueType> holder派生類,然后有
            一個提供給外部的any類。看了代碼后有一種讓我噴血的感覺。其實現(xiàn)原理和我自己的完全一致。
                比較而言,我覺得我的var_list撇腳到了極致。因為我封裝了容器,而這顯然是沒必要的,并且限制
            了其使用范圍。而boost::any則是僅僅封裝了類型。
                數(shù)據(jù)轉(zhuǎn)換方面,boost::any提供了any_cast和unsafe_any_cast。unsafe_any_cast和我這里用的轉(zhuǎn)換
            差不多,也就是我說的類型不安全。而他的any_cast呢?則是用到了typeid,多了次類型檢查而已。
                沒辦法,看來var_list需要被刪掉,直接搬boost::any過來吧,同樣地check out boost::any for more
            information...
                現(xiàn)在看來,boost真的很強大。我感覺再怎么偏門的需求,都能在boost里找到個實現(xiàn)。痛定思痛,決定
            把boost doc長期開著。

            posted @ 2008-10-15 11:23 Kevin Lynx 閱讀(4749) | 評論 (10)編輯 收藏

            代碼自動生成-宏遞歸思想

            Macro Recursion
            author: Kevin Lynx

            Preface

                本文可能是<代碼自動生成-宏帶來的奇技淫巧>的續(xù)寫。我盡力闡述如何讓宏遞歸(或者說重復(fù))地有規(guī)律地產(chǎn)生一
            些符號,而讓我們少寫很多重復(fù)代碼,也許這些代碼只有那么一點點的不同。將這項小技巧用于底層庫的編寫,會讓代碼
            看起來干凈不少,同時文件尺寸也會驟然下降。


            Problem


                如果你曾經(jīng)寫過functor,那么你肯定對某些代碼進(jìn)行粘貼復(fù)制然后修改。更讓人郁悶的是,這些代碼基本是一樣的。
            例如,一個典型的functor可能為:

                template <typename Prototype>
                
            class functor;
                template 
            <typename R, typename P1>
                
            class functor<R(P1)>;
                template 
            <typename R, typename P1, typename P2>
                
            class functor<R(P1,P2)>;


                //好,接下去你可能厭煩了,可能會復(fù)制一個帶有兩個參數(shù)的functor,然后修改為處理3個參數(shù)的。
                這只是一個很簡單的問題。宏不是c++里的東西,本文自然也不是討論各種花哨的模板技術(shù)的。如果我之前那篇關(guān)于
            宏的文章只是讓你去分析問題以及更深層次地認(rèn)識宏,那么現(xiàn)在我將分享我的這部分思想給你。
                關(guān)于上面的問題,我們期待得到這樣的解決方案:

                template <typename R, DEF_PARAM( 2 )>
                
            class functor<R( DEF_ARG( 2 ) )>;


                那么,它將自動生成:

                template <typename R, typename P1, typename P2>
                
            class functor<R(P1,P2)>


                也就是說,DEF_PARAM(n)這個宏將根據(jù)n值自動生成一串符號,例如DEF_PARAM(2)就生成typename P1, typename P2。
            同樣,DEF_ARG(n)也會根據(jù)參數(shù)生成類似于P1,P2,...,Pn的符號串。

            思考

                仔細(xì)思考下,我們可以看出DEF_PARAM和DEF_ARG這樣的宏具有一種遞歸的特性(其實說成重復(fù)可能更合適):每次展
            開的內(nèi)容基本一樣,不斷調(diào)用自身直到遇到終止條件。
                那么,我們的目標(biāo)鎖定于,用宏來實現(xiàn)遞歸。


            Pre-Implement

                在開始之前,我需要告訴你一些基本的東西:
                在閱讀一個宏時,你最好按照預(yù)處理的處理方式去逐個展開。當(dāng)我說到展開時,我的意思是把宏替換為宏體。預(yù)處理器
            展開宏的過程大致為:如果宏參數(shù)也是個宏,那么先將宏參數(shù)全部展開,再展開該宏;這個時候會掃描展開后的宏,如果
            遇到其他宏,則繼續(xù)展開。例如有一下宏:

             

            #define PI 3.14
            #define MUL_PI( n ) n * PI
            #define TWO 2


                當(dāng)我們寫下MUL_PI( TWO )時,預(yù)處理發(fā)現(xiàn)MUL_PI的參數(shù)TWO 是個宏,那么先將TWO展開得到2,然后將2放進(jìn)宏體展開
            得到 2 * PI;預(yù)處理器對 2 * PI 進(jìn)行掃描,發(fā)現(xiàn)還有宏P(guān)I,于是對PI做展開,得到 2 * 3.14。這個過程是遞歸的。
                但是也有例外,如果MUL_PI對宏參數(shù)進(jìn)行了#或者##,那么該宏參數(shù)不會被展開。(參見以前那篇文章吧)
                任何時候,你可以通過以下宏去查看某個宏展開后的樣子,可以方便你調(diào)試你的宏:

            #define TO_STRING( x ) TO_STRING1( x )
            #define TO_STRING1( x ) #x 


                (為什么要寫個TO_STRING1,因為這是為了讓x充分展開,避免上面提到的那個例外)   

                其他規(guī)則我會在文中需要的地方提出來。
            實現(xiàn)

                就像大部分介紹遞歸函數(shù)時候給的例子,這里我也將階乘作為例子。考慮如下典型的階乘函數(shù):

                int fac( int n )
                
            {
                    
            if( n == 1 ) return 1;
                    
            return n * fac( n - 1 );
                }
             


                其核心部分在于 n * fac( n - 1 ),我們假設(shè)我們的宏也可以寫成這樣的的形式:

                #define FAC( n ) n * FAC( n - 1 )


                但是這樣的宏是有問題的:
                當(dāng)宏被展開時,如果遇到了自身,那么將被處理為一般符號,例如展開FAC( 3 )時,會遇到 FAC( 2 ),那么就把FAC
            ( 2 )中的FAC當(dāng)成了一搬符號。
                這樣的限制注定了我們無法讓宏真正地調(diào)用自身來實現(xiàn)遞歸。于是,我們不得不寫下以下丑陋的符號,從而去模擬遞
            歸的每一次符號調(diào)用:

            #define FAC_1( n ) 1
            #define FAC_2( n ) n * FAC_##(n-1)( n - 1 )
            #define FAC_3( n ) n * FAC_##(n-1)( n - 1 ) 


                這系列宏有點別扭(如果你足夠細(xì)心),因為我們明顯知道FAC_2返回的將是2,而FAC_3返回的當(dāng)時6。我們這里只是
            模擬,同樣,這使得我們可以把FAC_1作為遞歸的終止條件。
                我們的預(yù)想是,當(dāng)調(diào)用FAC_3時,它把n-1的值2合并到FAC_中,從而調(diào)用FAC_2,以此類推。
                但是這依然有問題,編譯器會提示‘找不到符號FAC_’。導(dǎo)致這個問題的原因在于:宏展開只是單純的字符替換,是我們
            想太多了,預(yù)處理器并不會去計算( n - 1 )的值是多少,也就是我們無法得到FAC_2這個宏。

                所以,F(xiàn)AC_3( 3 ) 會被初次替換為 3 * FAC_(3-1)( 3 - 1 )。這個時候編譯器就把FAC_當(dāng)成了一個普通符號。我們可以
            自己定義個FAC_來證明這一點:

             

            #define FAC_( n ) T 

             

                那么,F(xiàn)AC_3( 3 )就被替換為 3 * T(3-1)( 3 - 1 )。

                解決這個問題的辦法關(guān)鍵在于,讓預(yù)處理器自動計算出( n - 1 )。記住,我們解決問題的唯一辦法是:字符替換。
            所以,我們可以寫下如下代碼:

             

            #define DEC_1 0
            #define DEC_2 1
            #define DEC_3 2 

            #define DEC( n ) DEC_##n 

             

                通過,DEC( n )這個宏,我們可以獲取到一個( n -1 )的數(shù)。例如,DEC( 3 )被替換為 DEC_3,繼續(xù)替換為 2。

                于是,我們新的FAC系列宏變?yōu)椋?

             

            #define FAC_1( n ) 1
            #define FAC_2( n ) n * FAC_##DEC( n )( n - 1 )
            #define FAC_3( n ) n * FAC_##DEC( n )( n - 1 ) 

             

                不好意思,這樣依然是不正確的!預(yù)處理器直接把FAC_和DEC( n )連接成符號了,而不是單個地先處理他們,最后再
            合并他們。

                OK,先解決這個問題:先處理FAC_和DEC( n ),再合并他們,而不是先合并他們。要解決這個問題,可以通過第三個宏
            來實現(xiàn):

             

            #define CHR( x, y ) x##y 

             

                作為連接兩個符號為一個符號的宏,這個宏顯然是不正確的,因為宏展開還有個規(guī)則:如果宏體對宏參數(shù)使用了#或##,
            那么宏參數(shù)不會被展開,也就是說:如果CHR( FAC_, DEC( 3 ) 那么得到的只會是 FAC_DEC( 3 )。通常情況下我們是
            再寫個宏:

             

            #define CHR( x, y ) CHR1( x, y )
            #define CHR1( x, y ) x##y 

             

                從而可以保證在正式連接x和y前,x和y都被完全展開。

                這個時候,我們的FAC系列宏變?yōu)椋?

             

            #define FAC_1( n ) 1
            #define FAC_2( n ) n * CHR( FAC_, DEC( n ) )( n - 1 )
            #define FAC_3( n ) n * CHR( FAC_, DEC( n ) )( n - 1 ) 

             

                結(jié)果呢?結(jié)果還是有問題。= =
                我們假設(shè)CHR( FAC_, DEC( n ) )已經(jīng)真的按我們預(yù)想展開為 FAC_2了,那么FAC_3( 3 )會被展開為什么呢?
            被展開為 3 * FAC_2( 3 - 1 )。這是錯誤的,傳給 FAC_2 的參數(shù)是 3 - 1就意味著錯誤。我們又臆想預(yù)處理器會
            幫我們計算 3 - 1的結(jié)果了。我們必須保證傳給 FAC_2的參數(shù)是個數(shù)字2。解決這個問題的辦法就是通過DEC(n)宏。

               于是,F(xiàn)AC系列宏變?yōu)椋?

             

            #define FAC_1( n ) 1
            #define FAC_2( n ) n * CHR( FAC_, DEC( n ) )( DEC( n ) )
            #define FAC_3( n ) n * CHR( FAC_, DEC( n ) )( DEC( n ) ) 

             

                這個時候,F(xiàn)AC_3( 3 )將會被替換為:3*2*1。這就是我們要的結(jié)果。

            In practice

                以上只是向你展示一個過程,用宏去計算階乘,就像用模板去計算階乘(模板元編程)一樣,只是一個用于展示的東西,
            沒有什么實際價值。接下來我們開始有實際的工作,完成之前的預(yù)想:

             

            template <typename R, typename P1, typename P2, typename P3>
            class functor<R (P1, P2, P3)> 

             

                直接:

             

            template <typename R, PARAM( 3 )>
            class functor<R (ARG( 3 ))> 

             

                先考慮PARAM宏,該宏的任務(wù)就是生成類似于:typename P1, typename P2, typename P3這樣的符號。我們假象它每一次
            遞歸都生成 typename Pn, 的字符串,那么當(dāng)他遞歸完時,可能就生成typename P1, typename P2, typename P3, 結(jié)果
            多了個逗號,也許最后一次結(jié)果不該有逗號。

                ARG宏和PARAM宏本質(zhì)上相同,只是重復(fù)的符號不是typename Pn,而是Pn。

                最直接想到的是:

             

            #define PARAM_1( n ) typename P##n
            #define PARAM_2( n ) CHR( PARAM_, DEC( n ) )( DEC( n ) )##,typename P##n
            #define PARAM_3( n ) CHR( PARAM_, DEC( n ) )( DEC( n ) )##,typename P##n 

             

                結(jié)果我們得到了個錯誤的展開結(jié)果:
            typename PDEC( 2 ),typename PDEC( 3 ),typename P3

                這個問題出在:PARAM_3( 3 )當(dāng)替換為 PARAM_2( DEC( n ) )時,因為PARAM_2(n)宏對于宏參數(shù)n使用了##,也就是那個
            typename P##n,所以這里不會把 DEC( n )展開,而是直接接到P后面。所以就成了typename PDEC( 3 )。

                為了消除這個問題,我們改進(jìn)PARAM為:

             

            #define TYPE( n ) ,typename P##n
            #define PARAM_1( n ) CHR( typename P, n )
            #define PARAM_2( n ) CHR( CHR( PARAM_, DEC( n ) )( DEC( n ) ), TYPE( n ) )
            #define PARAM_3( n ) CHR( CHR( PARAM_, DEC( n ) )( DEC( n ) ), TYPE( n ) ) 

             

                之所以加入TYPE(n),是因為 ,typename P##n 這個宏本身存在逗號,將其直接用于宏體會出現(xiàn)問題。

                于是,我們得到了正確的結(jié)果。

                其實,PARAM系列宏宏體基本是一樣的,除了終止條件那個宏,為什么我們要寫這么多呢?理由在于宏體不能自己調(diào)用
            自己,所以才有了PARAM_3, PARAM_2。

                我們可以將上面的一系列宏抽象化,使其具有可復(fù)用性:

             

            #define PARAM( n ) ,typename P##n
            #define PARAM_END typename P 

            #define ARG( n ) ,P##n
            #define ARG_END P 

            #define PARAM_1( n ) CHR( typename P, n )
            #define PARAM_2( n ) CHR( CHR( PARAM_, DEC( n ) )( DEC( n ) ), TYPE( n ) )
            #define PARAM_3( n ) CHR( CHR( PARAM_, DEC( n ) )( DEC( n ) ), TYPE( n ) ) 

            #define REPEAT_1( n, f, e ) CHR( e, n )
            #define REPEAT_2( n, f, e ) CHR( CHR( REPEAT_, DEC( n ) )( DEC( n ), f, e ), f( n ) )
            #define REPEAT_3( n, f, e ) CHR( CHR( REPEAT_, DEC( n ) )( DEC( n ), f, e ), f( n ) ) 

            #define DEF_PARAM( n ) REPEAT_##n( n, PARAM, PARAM_END )
            #define DEF_ARG( n ) REPEAT_##n( n, ARG, ARG_END ) 

             

                我們創(chuàng)建了可重用的REPEAT系列宏,用于創(chuàng)建諸如typename P1, typename P2, typename P3或者P1,P2,P3之類的符號,
            通過更上層的DEF_PARAM(n)和DEF_ARG(n),就可以直接創(chuàng)建出我們上面所需要的符號串,例如:

                DEF_PARAM( 3 ) 就得到 typename P1, typename P2, typename P3
                DEF_ARG( 3 ) 就得到 P1, P2, P3

            More in practice

                下載中提供了我使用這個宏遞歸技術(shù)寫的lua_binder(如果你看過<實現(xiàn)自己的LUA綁定器-一個模板編程挑戰(zhàn) >),你
            可以與上一個版本做一下比較,代碼少了很多。
                同樣,我希望你也能獲取這種宏遞歸的思想。   

            相關(guān)下載

               使用宏遞歸的lua_binder

            posted @ 2008-08-20 17:48 Kevin Lynx 閱讀(12338) | 評論 (25)編輯 收藏

            實現(xiàn)自己的LUA綁定器-一個模板編程挑戰(zhàn)

                 摘要: 實現(xiàn)LUA綁定器 author : Kevin Lynx Preface     當(dāng)LUA腳本調(diào)用我們注冊的C函數(shù)時,我們需要逐個地從LUA棧里取出調(diào)用參數(shù),當(dāng)函數(shù)返回時,又需要一個一個地往LUA棧壓入返回值,并且我們注冊的函數(shù)只能是int()(lua_State*)類型。這很不方便,對于上層程序員來說更不方便。    因此我們要做的...  閱讀全文

            posted @ 2008-08-13 09:33 Kevin Lynx 閱讀(6976) | 評論 (15)編輯 收藏

            僅列出標(biāo)題
            共12頁: First 4 5 6 7 8 9 10 11 12 
            久久免费高清视频| 久久亚洲精品国产精品| 久久久久九国产精品| 四虎影视久久久免费| 精产国品久久一二三产区区别 | 久久国产精品无码网站| 久久婷婷五月综合色99啪ak| 欧美伊人久久大香线蕉综合 | 97视频久久久| 久久棈精品久久久久久噜噜| 伊人色综合久久天天| 久久毛片一区二区| 久久久国产精品亚洲一区| 中文字幕亚洲综合久久| 亚洲精品NV久久久久久久久久| 人妻无码中文久久久久专区| 91久久精品国产成人久久| 亚洲第一永久AV网站久久精品男人的天堂AV | 91久久九九无码成人网站| 18禁黄久久久AAA片| 久久精品国产免费| 99久久香蕉国产线看观香| 国产精品久久久久久久久鸭 | 久久最近最新中文字幕大全| 亚洲国产天堂久久综合| 国产欧美一区二区久久| 久久天天躁狠狠躁夜夜躁2014| 亚洲一区中文字幕久久| 久久SE精品一区二区| 久久99精品久久久久久齐齐| 久久精品中文闷骚内射| 亚洲乱码日产精品a级毛片久久| www久久久天天com| 99久久香蕉国产线看观香| 久久91精品综合国产首页| 精品乱码久久久久久久| 久久婷婷五月综合97色直播| 久久精品成人免费观看97| 色成年激情久久综合| 久久99精品国产麻豆| 亚洲综合伊人久久大杳蕉|