• <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ù)__歡迎移步我的獨(dú)立博客 codemaro.com 微博 kevinlynx

            代碼自動(dòng)生成-宏帶來的奇技淫巧

            Author : Kevin Lynx

            眾多C++書籍都忠告我們C語言宏是萬惡之首,但事情總不如我們想象的那么壞,就如同goto一樣。宏有
            一個(gè)很大的作用,就是自動(dòng)為我們產(chǎn)生代碼。如果說模板可以為我們產(chǎn)生各種型別的代碼(型別替換),
            那么宏其實(shí)可以為我們?cè)诜?hào)上產(chǎn)生新的代碼(即符號(hào)替換、增加)。

            關(guān)于宏的一些語法問題,可以在google上找到。相信我,你對(duì)于宏的了解絕對(duì)沒你想象的那么多。如果你
            還不知道#和##,也不知道prescan,那么你肯定對(duì)宏的了解不夠。

            我稍微講解下宏的一些語法問題(說語法問題似乎不妥,macro只與preprocessor有關(guān),跟語義分析又無關(guān)):

            1. 宏可以像函數(shù)一樣被定義,例如:
               #define min(x,y) (x<y?x:y) //事實(shí)上這個(gè)宏存在BUG
               但是在實(shí)際使用時(shí),只有當(dāng)寫上min(),必須加括號(hào),min才會(huì)被作為宏展開,否則不做任何處理。
              
            2. 如果宏需要參數(shù),你可以不傳,編譯器會(huì)給你警告(宏參數(shù)不夠),但是這會(huì)導(dǎo)致錯(cuò)誤。如C++書籍中所描
               述的,編譯器(預(yù)處理器)對(duì)宏的語法檢查不夠,所以更多的檢查性工作得你自己來做。

            3. 很多程序員不知道的#和##
               #符號(hào)把一個(gè)符號(hào)直接轉(zhuǎn)換為字符串,例如:
               #define STRING(x) #x
               const char *str = STRING( test_string ); str的內(nèi)容就是"test_string",也就是說#會(huì)把其后的符號(hào)
               直接加上雙引號(hào)。
               ##符號(hào)會(huì)連接兩個(gè)符號(hào),從而產(chǎn)生新的符號(hào)(詞法層次),例如:
               #define SIGN( x ) INT_##x
               int SIGN( 1 ); 宏被展開后將成為:int INT_1;

            4. 變參宏,這個(gè)比較酷,它使得你可以定義類似的宏:
               #define LOG( format, ... ) printf( format, __VA_ARGS__ )
               LOG( "%s %d", str, count );
               __VA_ARGS__是系統(tǒng)預(yù)定義宏,被自動(dòng)替換為參數(shù)列表。

            5. 當(dāng)一個(gè)宏自己調(diào)用自己時(shí),會(huì)發(fā)生什么?例如:
               #define TEST( x ) ( x + TEST( x ) )
               TEST( 1 ); 會(huì)發(fā)生什么?為了防止無限制遞歸展開,語法規(guī)定,當(dāng)一個(gè)宏遇到自己時(shí),就停止展開,也就是
               說,當(dāng)對(duì)TEST( 1 )進(jìn)行展開時(shí),展開過程中又發(fā)現(xiàn)了一個(gè)TEST,那么就將這個(gè)TEST當(dāng)作一般的符號(hào)。TEST(1)
               最終被展開為:1 + TEST( 1) 。

            6. 宏參數(shù)的prescan,
               當(dāng)一個(gè)宏參數(shù)被放進(jìn)宏體時(shí),這個(gè)宏參數(shù)會(huì)首先被全部展開(有例外,見下文)。當(dāng)展開后的宏參數(shù)被放進(jìn)宏體時(shí),
               預(yù)處理器對(duì)新展開的宏體進(jìn)行第二次掃描,并繼續(xù)展開。例如:
               #define PARAM( x ) x
               #define ADDPARAM( x ) INT_##x
               PARAM( ADDPARAM( 1 ) );
               因?yàn)锳DDPARAM( 1 ) 是作為PARAM的宏參數(shù),所以先將ADDPARAM( 1 )展開為INT_1,然后再將INT_1放進(jìn)PARAM。
              
               例外情況是,如果PARAM宏里對(duì)宏參數(shù)使用了#或##,那么宏參數(shù)不會(huì)被展開:
               #define PARAM( x ) #x
               #define ADDPARAM( x ) INT_##x
               PARAM( ADDPARAM( 1 ) ); 將被展開為"ADDPARAM( 1 )"。

               使用這么一個(gè)規(guī)則,可以創(chuàng)建一個(gè)很有趣的技術(shù):打印出一個(gè)宏被展開后的樣子,這樣可以方便你分析代碼:
               #define TO_STRING( x ) TO_STRING1( x )
               #define TO_STRING1( x ) #x
               TO_STRING首先會(huì)將x全部展開(如果x也是一個(gè)宏的話),然后再傳給TO_STRING1轉(zhuǎn)換為字符串,現(xiàn)在你可以這樣:
               const char *str = TO_STRING( PARAM( ADDPARAM( 1 ) ) );去一探PARAM展開后的樣子。

            7. 一個(gè)很重要的補(bǔ)充:就像我在第一點(diǎn)說的那樣,如果一個(gè)像函數(shù)的宏在使用時(shí)沒有出現(xiàn)括號(hào),那么預(yù)處理器只是
               將這個(gè)宏作為一般的符號(hào)處理(那就是不處理)。


            我們來見識(shí)一下宏是如何幫助我們自動(dòng)產(chǎn)生代碼的。如我所說,宏是在符號(hào)層次產(chǎn)生代碼。我在分析Boost.Function
            模塊時(shí),因?yàn)樗褂昧舜罅康暮?宏嵌套,再嵌套),導(dǎo)致我壓根沒看明白代碼。后來發(fā)現(xiàn)了一個(gè)小型的模板庫(kù)ttl,說的
            是開發(fā)一些小型組件去取代部分Boost(這是一個(gè)好理由,因?yàn)锽oost確實(shí)太大)。同樣,這個(gè)庫(kù)也包含了一個(gè)function庫(kù)。
            這里的function也就是我之前提到的functor。ttl.function庫(kù)里為了自動(dòng)產(chǎn)生很多類似的代碼,使用了一個(gè)宏:

            #define TTL_FUNC_BUILD_FUNCTOR_CALLER(n)  \
             template< typename R, TTL_TPARAMS(n) > \
             struct functor_caller_base##n \
                    ///...
            該宏的最終目的是:通過類似于TTL_FUNC_BUILD_FUNCTOR_CALLER(1)的調(diào)用方式,自動(dòng)產(chǎn)生很多functor_caller_base模板:
            template <typename R, typename T1> struct functor_caller_base1
            template <typename R, typename T1, typename T2> struct functor_caller_base2
            template <typename R, typename T1, typename T2, typename T3> struct functor_caller_base3
            ///...
            那么,核心部分在于TTL_TPARAMS(n)這個(gè)宏,可以看出這個(gè)宏最終產(chǎn)生的是:
            typename T1
            typename T1, typename T2
            typename T1, typename T2, typename T3
            ///...
            我們不妨分析TTL_TPARAMS(n)的整個(gè)過程。分析宏主要把握我以上提到的一些要點(diǎn)即可。以下過程我建議你翻著ttl的代碼,
            相關(guān)代碼文件:function.hpp, macro_params.hpp, macro_repeat.hpp, macro_misc.hpp, macro_counter.hpp。

            so, here we go

            分析過程,逐層分析,逐層展開,例如TTL_TPARAMS(1):

            #define TTL_TPARAMS(n) TTL_TPARAMSX(n,T) 
            => TTL_TPARAMSX( 1, T )
            #define TTL_TPARAMSX(n,t) TTL_REPEAT(n, TTL_TPARAM, TTL_TPARAM_END, t)
            => TTL_REPEAT( 1, TTL_TPARAM, TTL_TPARAM_END, T )
            #define TTL_TPARAM(n,t) typename t##n,
            #define TTL_TPARAM_END(n,t) typename t##n
            #define TTL_REPEAT(n, m, l, p) TTL_APPEND(TTL_REPEAT_, TTL_DEC(n))(m,l,p) TTL_APPEND(TTL_LAST_REPEAT_,n)(l,p)
            注意,TTL_TPARAM, TTL_TPARAM_END雖然也是兩個(gè)宏,他們被作為TTL_REPEAT宏的參數(shù),按照prescan規(guī)則,似乎應(yīng)該先將
            這兩個(gè)宏展開再傳給TTL_REPEAT。但是,如同我在前面重點(diǎn)提到的,這兩個(gè)宏是function-like macro,使用時(shí)需要加括號(hào),
            如果沒加括號(hào),則不當(dāng)作宏處理。因此,展開TTL_REPEAT時(shí),應(yīng)該為:
            => TTL_APPEND( TTL_REPEAT_, TTL_DEC(1))(TTL_TPARAM,TTL_TPARAM_END,T) TTL_APPEND( TTL_LAST_REPEAT_,1)(
            TTL_TPARAM_END,T)
            這個(gè)宏體看起來很復(fù)雜,仔細(xì)分析下,可以分為兩部分:
            TTL_APPEND( TTL_REPEAT_, TTL_DEC(1))(TTL_TPARAM,TTL_TPARAM_END,T)以及
            TTL_APPEND( TTL_LAST_REPEAT_,1)(TTL_TPARAM_END,T)
            先分析第一部分:
            #define TTL_APPEND( x, y ) TTL_APPEND1(x,y) //先展開x,y再將x,y連接起來
            #define TTL_APPEND1( x, y ) x ## y
            #define TTL_DEC(n) TTL_APPEND(TTL_CNTDEC_, n)
            根據(jù)先展開參數(shù)的原則,會(huì)先展開TTL_DEC(1)
            => TTL_APPEND(TTL_CNTDEC_,1) => TTL_CNTDEC_1
            #define TTL_CNTDEC_1 0  注意,TTL_CNTDEC_不是宏,TTL_CNTDEC_1是一個(gè)宏。
            => 0 , 也就是說,TTL_DEC(1)最終被展開為0。回到TTL_APPEND部分:
            => TTL_REPEAT_0 (TTL_TPARAM,TTL_TPARAM_END,T)
            #define TTL_REPEAT_0(m,l,p)
            TTL_REPEAT_0這個(gè)宏為空,那么,上面說的第一部分被忽略,現(xiàn)在只剩下第二部分:
            TTL_APPEND( TTL_LAST_REPEAT_,1)(TTL_TPARAM_END,T)
            => TTL_LAST_REPEAT_1 (TTL_TPARAM_END,T) // TTL_APPEND將TTL_LAST_REPEAT_和1合并起來
            #define TTL_LAST_REPEAT_1(m,p) m(1,p)
            => TTL_TPARAM_END( 1, T )
            #define TTL_TPARAM_END(n,t) typename t##n
            => typename T1  展開完畢。

            雖然我們分析出來了,但是這其實(shí)并不是我們想要的。我們應(yīng)該從那些宏里去獲取作者關(guān)于宏的編程思想。很好地使用宏
            看上去似乎是一些偏門的奇技淫巧,但是他確實(shí)可以讓我們編碼更自動(dòng)化。

            參考資料:
            macro語法: http://developer.apple.com/documentation/DeveloperTools/gcc-4.0.1/cpp/Macros.html
            ttl(tiny template library) : http://tinytl.sourceforge.net/

              
             

            posted on 2008-03-19 10:34 Kevin Lynx 閱讀(14399) 評(píng)論(7)  編輯 收藏 引用 所屬分類: c/c++

            評(píng)論

            # re: 代碼自動(dòng)生成-宏帶來的奇技淫巧[未登錄] 2008-03-19 11:31 cppexplore

            不錯(cuò)!
            c語言的程序里經(jīng)常是遍地的宏 遍地指針的精巧使用

            宏還是非常不錯(cuò)的 類似c語言中的模版機(jī)制
            內(nèi)核里的經(jīng)典數(shù)據(jù)結(jié)構(gòu)及其算法,象SLIST LIST TAILQ之類的都是宏寫的  回復(fù)  更多評(píng)論   

            # re: 代碼自動(dòng)生成-宏帶來的奇技淫巧 2008-03-19 18:08 酷勤網(wǎng)

            代碼編寫,不應(yīng)該過分使用“技巧” 有些看似技巧的東西,實(shí)質(zhì)沒有多大價(jià)值。  回復(fù)  更多評(píng)論   

            # re: 代碼自動(dòng)生成-宏帶來的奇技淫巧 2008-03-25 10:16 suxiaojack

            @酷勤網(wǎng)
            no,no!這根本不是技巧,是語言的形式化抽象,把很長(zhǎng)的東西,一層層映射到短小的上去。編程要做的一部分工作也就是這個(gè)。  回復(fù)  更多評(píng)論   

            # re: 代碼自動(dòng)生成-宏帶來的奇技淫巧[未登錄] 2008-03-25 11:32 cppexplore

            @suxiaojack
            呵呵 一語中的!文中的內(nèi)容的確不是技巧,都是能優(yōu)雅解決實(shí)際問題的東東  回復(fù)  更多評(píng)論   

            # re: 代碼自動(dòng)生成-宏帶來的奇技淫巧 2008-05-10 10:38 zhang某人

            做日志還是很爽的~~  回復(fù)  更多評(píng)論   

            # re: 代碼自動(dòng)生成-宏帶來的奇技淫巧 2008-12-10 12:27 秒大刀

            在不會(huì)特別厭煩的一般情況下,請(qǐng)避免使用宏
            即使是min和max也不如std::min和std::max  回復(fù)  更多評(píng)論   

            # re: 代碼自動(dòng)生成-宏帶來的奇技淫巧 2012-02-09 12:17 hcconquer

            @秒大刀
            為啥  回復(fù)  更多評(píng)論   

            国产精品久久久久久福利漫画| 久久亚洲色一区二区三区| 亚洲午夜久久久久久久久久| 亚洲国产精品久久久天堂| 精品一区二区久久| 久久婷婷午色综合夜啪| 人妻久久久一区二区三区| 色噜噜狠狠先锋影音久久| 亚洲v国产v天堂a无码久久| 99国产精品久久| 久久精品国产久精国产一老狼| 久久96国产精品久久久| 久久久久亚洲AV无码永不| 久久www免费人成精品香蕉| 久久精品黄AA片一区二区三区| 伊人久久亚洲综合影院| A级毛片无码久久精品免费| 久久99精品国产自在现线小黄鸭| 欧美精品乱码99久久蜜桃| 精品久久久久久国产免费了| 99久久精品费精品国产一区二区 | 久久精品成人免费看| 国产精品久久久久免费a∨| 久久精品国产欧美日韩| 国产精品无码久久综合| 看久久久久久a级毛片| 久久精品视频一| 国产精品成人久久久| 伊人久久大香线蕉综合网站| 亚洲精品国产自在久久| 午夜精品久久久久| 久久亚洲AV成人无码软件| 亚洲日本久久久午夜精品| 日本久久久久久久久久| 无夜精品久久久久久| 国产精品久久婷婷六月丁香| 麻豆精品久久久久久久99蜜桃| 久久精品国产乱子伦| 婷婷伊人久久大香线蕉AV| 97久久精品午夜一区二区| 久久免费小视频|