• <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>

            巢穴

            about:blank

            2011年3月15日 #

            linux內核情景分析筆記-存儲管理

            第2章 存儲管理
            LINUX頁式管理
            PGD          PMD          PT        PTE
            頁表目標     中間目錄     頁表     頁表項

            LINUX在32位地址下采取二層映射
            #define PGDIR_SHIFT 22
            #define PTRS_PER_PGD 1024

            #define PMD_SHIFT 22
            #define PTRS_PER_PMD 1

            #define PTRS_PER_PTE 1024
            根據以上宏定義,PMD被完美的架空了,而相當于采取了二層映射

            其中PGD用了線性地址的最高10位 與  MMU 對應
            線性地址的中間10位是所對應的PTE在PT中的索引
            剩下的最低12位則是頁中的偏移量

            虛擬地址 = 段基地址:段偏移量
                                        16位      32位
            更準確的講是段選擇子了吧

            在LINUX中段基地址 = 0(下面的____KERNEL_CS等),所以可以認為線性地址與虛擬地址總是相等的,但其本質不是一個東西


            0xC0000000-0xFFFFFFFF為內核占用
            0x0-0xBFFFFFFF為用戶控件


            內核的虛擬內存為簡單的線性映射
            #__PAGE_OFFSET (0xC0000000)
            #define PAGE_OFFSET  ((unsigned long) __PAGE_OFFSET)
            #define __pa(x) ((unsigned long)(x) - PAGE_OFFSET)
            #define __va(x) ((void *)((unsigned long)(x) +PAGE_OFFSET)

            __pa是從虛擬地址轉換成物理地址
            __va是從物理地址轉換成虛擬地址


            在GDT中有4個段描述符
            其索引是2-5
            分別是
            __KERNEL_CS 內核代碼段
            __KERNEL_DS 內核數據段
            __USER_CS 用戶代碼段
            __USER_DS 用戶數據段

            #define start_thread(regs,new_eip,new_esp) do {\
             __asm__("movl %0,%%fs;movl %0,%%gs"::"r"(0)); \
             set_fs(USER_DS);
             regs->xds = __USER_DS; \
             regs->xes = __USER_DS; \
             regs->xss = __USER_DS; \
             regs->xcs = __USER_CS; \
             regs->eip = new_eip;   \
             regs->esp = new_esp;   \

            }while(0)

            通過這段宏可以看出,LINUX沒用段式存儲,雖然它也走了這個流程

             

            MMU的流程 MMU使用物理地址

            頁式映射
            從REG CR3拿PGD的地址
            找到頁面目錄,線性地址中的高10位為索引,找到頁面目錄項,從中拿高20位作為頁面表的索引,頁面表與4k字節邊界對齊,CPU自動補充前12位為0得到頁面表地址。

            然后拿線性地址的中間10位,得到頁面表中的索引,拿到頁面表項,頁面表項的高20位在低位補充12個0,再加上線性地址的低12位組成物理地址。


            mm_struct 任務相關的虛擬內存
            vm_area_struct 一段虛擬內存的抽象,也可以理解為段
            mm_struct中擁有vm_area_struct的指針
            在vm_area_struct多的時候使用avl樹來存儲
            mem_map_t  物理頁表
            zone_struct 物理內存的區結構,zone_struct把物理內存分成了幾個部分
            ZONE_DMA 0 供DMA使用
            ZONE_NORMAL 普通使用
            ZONE_HIGHMEN 高段內存,內核映射不到

            物理內存之間區的劃分并不是強制的,如果某一個區已經沒有內存可用,是可以去別的區拿內存的

            其實一直對內核的尋址有些疑問
            不過剛剛似乎想通了
            內核會做預映射,把PGD第768項以后的都做映射,也就是1G的空間
            而這種映射應該是滿足__pa()宏,即線性地址與物理地址是線性映射的。
            所以最終__pa()宏被用作在內核代碼中顯性的獲得某個線性地址所對應的物理地址
            而MMU負責把一個線性地址隱式的轉成了物理地址,而這已轉換與內核代碼無關。
            不知這樣理解是否正確?

            今天只看到了這里
            待續……

            說起來把這么個東西放到首頁很不好意思,主要目的是希望有看到的人幫我指正一下我所認知的錯誤或者解惑。謝謝啦:)

            posted @ 2011-03-15 17:47 Vincent 閱讀(1663) | 評論 (0)編輯 收藏

            2011年3月3日 #

            關于CppUnit的使用?

                首先,我是初學者,從無使用CppUnit的經驗。

                項目進展到后期,想提高穩定性,在這個時候是否適合加入單元測試呢?
                找了幾篇文章看了看,似乎談單元測試就不得不談TDD,但我看到的絕大多數人的觀點是舍TDD,留單元測試
                好了,暫且不談TDD,只談單元測試,其中我看到一篇Blog的觀點是,使用單元測試,來逐步重構代碼,其論點就是真正適合單元測試的代碼,能夠更符合SOLID原則。
                 這個看法是我目前比較認同的。
                 另外看到一種使用方式是,只對算法類和一些基礎類,進行單元測試。對于這種使用方式,我實不知使用CppUnit這種現成的較為自動化的單元測試的框架意義在哪。
                最后一個問題就是,如果我認為的前一種觀點是正確的,那在我們的這個項目進行到的這個階段,想要提高穩定性去做單元測試,是需要大量重構的,這樣一個量會不會反而會影響穩定性?


                 歡迎大家隨意發表自己的觀點哈,我只是想多了解一下。以便做出認為自己更適合的判斷。

            posted @ 2011-03-03 09:56 Vincent 閱讀(2381) | 評論 (11)編輯 收藏

            2011年2月15日 #

            關于lua的coroutine

            本質是串行,沒有同步開銷
            雖然在用戶態下,但是還是需要維護coroutine的狀態,
            也就是也會擁有自己的上下文切換的開銷
            更像是用戶態下實現的線程,但搶占式的線程不論切換粒度再大或再小,都會有不可預知的行為,所要做同步,而coroutine是協作式的任務,
            由自己交把自己的權利交出去,同步問題自然不用考慮了
            自己小記一下

            posted @ 2011-02-15 11:01 Vincent 閱讀(351) | 評論 (0)編輯 收藏

            Lisp的本質(The Nature of Lisp)(轉)

                                   Lisp的本質(The Nature of Lisp)

                                         作者 Slava Akhmechet
                                         譯者 Alec Jang

                          出處: http://www.defmacro.org/ramblings/lisp.html


            簡介

            最初在web的某些角落偶然看到有人贊美Lisp時, 我那時已經是一個頗有經驗的程序員。
            在我的履歷上, 掌握的語言范圍相當廣泛, 象C++, Java, C#主流語言等等都不在話下,
            我覺得我差不多知道所有的有關編程語言的事情。對待編程語言的問題上, 我覺得自己不
            太會遇到什么大問題。其實我大錯特錯了。

            我試著學了一下Lisp, 結果馬上就撞了墻。我被那些范例代碼嚇壞了。我想很多初次接觸
            Lisp語言的人, 一定也有過類似的感受。Lisp的語法太次了。一個語言的發明人, 居然不
            肯用心弄出一套漂亮的語法, 那誰還會愿意學它。反正, 我是確確實實被那些難看的無數
            的括號搞蒙了。

            回過神來之后, 我和Lisp社區的那伙人交談, 訴說我的沮喪心情。結果, 立馬就有一大套
            理論砸過來, 這套理論在Lisp社區處處可見, 幾成慣例。比如說: Lisp的括號只是表面現
            象; Lisp的代碼和數據的表達方式沒有差別, 而且比XML語法高明許多, 所以有無窮的好
            處; Lisp有強大無比的元語言能力, 程序員可以寫出自我維護的代碼; Lisp可以創造出針
            對特定應用的語言子集; Lisp的運行時和編譯時沒有明確的分界; 等等, 等等, 等等。這
            么長的贊美詞雖然看起來相當動人, 不過對我毫無意義。沒人能給我演示這些東西是如何
            應用的, 因為這些東西一般來說只有在大型系統才會用到。我爭辯說, 這些東西傳統語言
            一樣辦得到。在和別人爭論了數個小時之后, 我最終還是放棄了學Lisp的念頭。為什么要
            花費幾個月的時間學習語法這么難看的語言呢? 這種語言的概念這么晦澀, 又沒什么好懂
            的例子。也許這語言不是該我這樣的人學的。

            幾個月來, 我承受著這些Lisp辯護士對我心靈的重壓。我一度陷入了困惑。我認識一些絕
            頂聰明的人, 我對他們相當尊敬, 我看到他們對Lisp的贊美達到了宗教般的高度。這就是
            說, Lisp中一定有某種神秘的東西存在, 我不能忍受自己對此的無知, 好奇心和求知欲最
            終不可遏制。我于是咬緊牙關埋頭學習Lisp, 經過幾個月的時間費勁心力的練習, 終于,
            我看到了那無窮無盡的泉水的源頭。在經過脫胎換骨的磨練之后, 在經過七重地獄的煎熬
            之后, 終于, 我明白了。

            頓悟在突然之間來臨。曾經許多次, 我聽到別人引用雷蒙德(譯者注: 論文<<大教堂和市
            集>>的作者, 著名的黑客社區理論家)的話: "Lisp語言值得學習。當你學會Lisp之后, 你
            會擁有深刻的體驗。就算你平常并不用Lisp編程, 它也會使你成為更加優秀的程序員"。
            過去, 我根本不懂這些話的含義, 我也不相信這是真的。可是現在我懂得了。這些話蘊含
            的真理遠遠超過我過去的想像。我內心體會到一種神圣的情感, 一瞬間的頓悟, 幾乎使我
            對電腦科學的觀念發生了根本的改變。

            頓悟的那一刻, 我成了Lisp的崇拜者。我體驗到了宗教大師的感受: 一定要把我的知識傳
            布開來, 至少要讓10個迷失的靈魂得到拯救。按照通常的辦法, 我把這些道理(就是剛開
            始別人砸過來的那一套, 不過現在我明白了真實的含義)告訴旁人。結果太令人失望了,
            只有少數幾個人在我堅持之下, 發生了一點興趣, 但是僅僅看了幾眼Lisp代碼, 他們就退
            卻了。照這樣的辦法, 也許費數年功夫能造就了幾個Lisp迷, 但我覺得這樣的結果太差強
            人意了, 我得想一套有更好的辦法。

            我深入地思考了這個問題。是不是Lisp有什么很艱深的東西, 令得那么多老練的程序員都
            不能領會? 不是, 沒有任何絕對艱深的東西。因為我能弄懂, 我相信其他人也一定能。那
            么問題出在那里? 后來我終于找到了答案。我的結論就是, 凡是教人學高級概念, 一定要
            從他已經懂得的東西開始。如果學習過程很有趣, 學習的內容表達得很恰當, 新概念就會
            變得相當直觀。這就是我的答案。所謂元編程, 所謂數據和代碼形式合一, 所謂自修改代
            碼, 所謂特定應用的子語言, 所有這些概念根本就是同族概念, 彼此互為解釋, 肯定越講
            越不明白。還是從實際的例子出發最有用。

            我把我的想法說給Lisp程序員聽, 遭到了他們的反對。"這些東西本身當然不可能用熟悉
            的知識來解釋, 這些概念完全與眾不同, 你不可能在別人已有的經驗里找到類似的東西",
            可是我認為這些都是遁詞。他們又反問我, "你自己為啥不試一下?" 好吧, 我來試一下。
            這篇文章就是我嘗試的結果。我要用熟悉的直觀的方法來解釋Lisp, 我希望有勇氣的人讀
            完它, 拿杯飲料, 深呼吸一下, 準備被搞得暈頭轉向。來吧, 愿你獲得大能。

            重新審視XML

            千里之行始于足下。讓我們的第一步從XML開始。可是XML已經說得更多的了, 還能有什么
            新意思可說呢? 有的。XML自身雖然談談不上有趣, 但是XML和Lisp的關系卻相當有趣。
            XML和Lisp的概念有著驚人的相似之處。XML是我們通向理解Lisp的橋梁。好吧, 我們且把
            XML當作活馬醫。讓我們拿好手杖, 對XML的無人涉及的荒原地帶作一番探險。我們要從一
            個全新的視角來考察這個題目。

            表面上看, XML是一種標準化語法, 它以適合人閱讀的格式來表達任意的層次化數據
            (hirearchical data)。象任務表(to-do list), 網頁, 病歷, 汽車保險單, 配置文件等
            等, 都是XML用武的地方。比如我們拿任務表做例子:

            <todo name="housework">
                <item priority="high">Clean the house.</item>
                <item priority="medium">Wash the dishes.</item>
                <item priority="medium">Buy more soap.</item>
            </todo>

            解析這段數據時會發生什么情況? 解析之后的數據在內存中怎樣表示? 顯然, 用樹來表示
            這種層次化數據是很恰當的。說到底, XML這種比較容易閱讀的數據格式, 就是樹型結構
            數據經過序列化之后的結果。任何可以用樹來表示的數據, 同樣可以用XML來表示, 反之
            亦然。希望你能懂得這一點, 這對下面的內容極其重要。

            再進一步。還有什么類型的數據也常用樹來表示? 無疑列表(list)也是一種。上過編譯課
            吧? 還模模糊糊記得一點吧? 源代碼在解析之后也是用樹結構來存放的, 任何編譯程序都
            會把源代碼解析成一棵抽象語法樹, 這樣的表示法很恰當, 因為源代碼就是層次結構的:
            函數包含參數和代碼塊, 代碼快包含表達式和語句, 語句包含變量和運算符等等。

            我們已經知道, 任何樹結構都可以輕而易舉的寫成XML, 而任何代碼都會解析成樹, 因此,
            任何代碼都可以轉換成XML, 對不對? 我舉個例子, 請看下面的函數:

            int add(int arg1, int arg2)
            {
                return arg1+arg2;
            }

            能把這個函數變成對等的XML格式嗎? 當然可以。我們可以用很多種方式做到, 下面是其
            中的一種, 十分簡單:

            <define-function return-type="int" name="add">
                <arguments>
                    <argument type="int">arg1</argument>
                    <argument type="int">arg2</argument>
                </arguments>
                <body>
                    <return>
                        <add value1="arg1" value2="arg2" />
                    </return>
                </body>
            </define>

            這個例子非常簡單, 用哪種語言來做都不會有太大問題。我們可以把任何程序碼轉成XML,
            也可以把XML轉回到原來的程序碼。我們可以寫一個轉換器, 把Java代碼轉成XML, 另一個
            轉換器把XML轉回到Java。一樣的道理, 這種手段也可以用來對付C++(這樣做跟發瘋差不
            多么。可是的確有人在做, 看看GCC-XML(http://www.gccxml.org)就知道了)。進一步說,
            凡是有相同語言特性而語法不同的語言, 都可以把XML當作中介來互相轉換代碼。實際上
            幾乎所有的主流語言都在一定程度上滿足這個條件。我們可以把XML作為一種中間表示法,
            在兩種語言之間互相譯碼。比方說, 我們可以用Java2XML把Java代碼轉換成XML, 然后用
            XML2CPP再把XML轉換成C++代碼, 運氣好的話, 就是說, 如果我們小心避免使用那些C++不
            具備的Java特性的話, 我們可以得到完好的C++程序。這辦法怎么樣, 漂亮吧?

            這一切充分說明, 我們可以把XML作為源代碼的通用存儲方式, 其實我們能夠產生一整套
            使用統一語法的程序語言, 也能寫出轉換器, 把已有代碼轉換成XML格式。如果真的采納
            這種辦法, 各種語言的編譯器就用不著自己寫語法解析了, 它們可以直接用XML的語法解
            析來直接生成抽象語法樹。

            說到這里你該問了, 我們研究了這半天XML, 這和Lisp有什么關系呢? 畢竟XML出來之時,
            Lisp早已經問世三十年了。這里我可以保證, 你馬上就會明白。不過在繼續解釋之前, 我
            們先做一個小小的思維練習。看一下上面這個XML版本的add函數例子, 你怎樣給它分類,
            是代碼還是數據? 不用太多考慮都能明白, 把它分到哪一類都講得通。它是XML, 它是標
            準格式的數據。我們也知道, 它可以通過內存中的樹結構來生成(GCC-XML做的就是這個事
            情)。它保存在不可執行的文件中。我們可以把它解析成樹節點, 然后做任意的轉換。顯
            而易見, 它是數據。不過且慢, 雖然它語法有點陌生, 可它又確確實實是一個add函數,
            對吧?  一旦經過解析, 它就可以拿給編譯器編譯執行。我們可以輕而易舉寫出這個XML
            代碼解釋器, 并且直接運行它。或者我們也可以把它譯成Java或C++代碼, 然后再編譯運
            行。所以說, 它也是代碼。

            我們說到那里了? 不錯, 我們已經發現了一個有趣的關鍵之點。過去被認為很難解的概念
            已經非常直觀非常簡單的顯現出來。代碼也是數據, 并且從來都是如此。這聽起來瘋瘋癲
            癲的, 實際上卻是必然之事。我許諾過會以一種全新的方式來解釋Lisp, 我要重申我的許
            諾。但是我們此刻還沒有到預定的地方, 所以還是先繼續上邊的討論。

            剛才我說過, 我們可以非常簡單地實現XML版的add函數解釋器, 這聽起來好像不過是說說
            而已。誰真的會動手做一下呢? 未必有多少人會認真對待這件事。隨便說說, 并不打算真
            的去做, 這樣的事情你在生活中恐怕也遇到吧。你明白我這樣說的意思吧, 我說的有沒有
            打動你? 有哇, 那好, 我們繼續。

            重新審視Ant

            我們現在已經來到了月亮背光的那一面, 先別忙著離開。再探索一下, 看看我們還能發現
            什么東西。閉上眼睛, 想一想2000年冬天的那個雨夜, 一個名叫James Duncan Davidson
            的杰出的程序員正在研究Tomcat的servlet容器。那時, 他正小心地保存好剛修改過的文
            件, 然后執行make。結果冒出了一大堆錯誤, 顯然有什么東西搞錯了。經過仔細檢查, 他
            想, 難道是因為tab前面加了個空格而導致命令不能執行嗎? 確實如此。老是這樣, 他真
            的受夠了。烏云背后的月亮給了他啟示, 他創建了一個新的Java項目, 然后寫了一個簡單
            但是十分有用的工具, 這個工具巧妙地利用了Java屬性文件中的信息來構造工程, 現在
            James可以寫makefile的替代品, 它能起到相同的作用, 而形式更加優美, 也不用擔心有
            makefile那樣可恨的空格問題。這個工具能夠自動解釋屬性文件, 然后采取正確的動作來
            編譯工程。真是簡單而優美。

            (作者注: 我不認識James, James也不認識我, 這個故事是根據網上關于Ant歷史的帖子
            虛構的)

            使用Ant構造Tomcat之后幾個月, 他越來越感到Java的屬性文件不足以表達復雜的構造指
            令。文件需要檢出, 拷貝, 編譯, 發到另外一臺機器, 進行單元測試。要是出錯, 就發郵
            件給相關人員, 要是成功, 就繼續在盡可能高層的卷(volumn)上執行構造。追蹤到最后,
            卷要回復到最初的水平上。確實, Java的屬性文件不夠用了, James需要更有彈性的解決
            方案。他不想自己寫解析器(因為他更希望有一個具有工業標準的方案)。XML看起來是個
            不錯的選擇。他花了幾天工夫把Ant移植到XML,于是,一件偉大的工具誕生了。

            Ant是怎樣工作的?原理非常簡單。Ant把包含有構造命令的XML文件(算代碼還是算數據,
            你自己想吧),交給一個Java程序來解析每一個元素,實際情況比我說的還要簡單得多。
            一個簡單的XML指令會導致具有相同名字的Java類裝入,并執行其代碼。

                <copy todir="../new/dir">
                    <fileset dir="src_dir" />
                </copy>

            這段文字的含義是把源目錄復制到目標目錄,Ant會找到一個"copy"任務(實際上就是一個
            Java類), 通過調用Java的方法來設置適當參數(todir和fileset),然后執行這個任務。
            Ant帶有一組核心類, 可以由用戶任意擴展, 只要遵守若干約定就可以。Ant找到這些類,
            每當遇到XML元素有同樣的名字, 就執行相應的代碼。過程非常簡單。Ant做到了我們前面
            所說的東西: 它是一個語言解釋器, 以XML作為語法, 把XML元素轉譯為適當的Java指令。
            我們可以寫一個"add"任務, 然后, 當發現XML中有add描述的時候, 就執行這個add任務。
            由于Ant是非常流行的項目, 前面展示的策略就顯得更為明智。畢竟, 這個工具每天差不
            多有幾千家公司在使用。

            到目前為之, 我還沒有說Ant在解析XML時所遇到困難。你也不用麻煩去它的網站上去找答
            案了, 不會找到有價值的東西。至少對我們這個論題來說是如此。我們還是繼續下一步討
            論吧。我們答案就在那里。

            為什么是XML

            有時候正確的決策并非完全出于深思熟慮。我不知道James選擇XML是否出于深思熟慮。也
            許僅僅是個下意識的決定。至少從James在Ant網站上發表的文章看起來, 他所說的理由完
            全是似是而非。他的主要理由是移植性和擴展性, 在Ant案例上, 我看不出這兩條有什么
            幫助。使用XML而不是Java代碼, 到底有什么好處? 為什么不寫一組Java類, 提供api來滿
            足基本任務(拷貝目錄, 編譯等等), 然后在Java里直接調用這些代碼? 這樣做仍然可以保
            證移植性, 擴展性也是毫無疑問的。而且語法也更為熟悉, 看著順眼。那為什么要用 XML
            呢? 有什么更好的理由嗎?

            有的。雖然我不確定James是否確實意識到了。在語義的可構造性方面, XML的彈性是Java
            望塵莫及的。我不想用高深莫測的名詞來嚇唬你, 其中的道理相當簡單, 解釋起來并不費
            很多功夫。好, 做好預備動作, 我們馬上就要朝向頓悟的時刻做奮力一躍。

            上面的那個copy的例子, 用Java代碼怎樣實現呢? 我們可以這樣做:

                CopyTask copy = new CopyTask();
                Fileset fileset = new Fileset();

                fileset.setDir("src_dir");
                copy.setToDir("../new/dir");
                copy.setFileset(fileset);

                copy.excute();

            這個代碼看起來和XML的那個很相似, 只是稍微長一點。差別在那里? 差別在于XML構造了
            一個特殊的copy動詞, 如果我們硬要用Java來寫的話, 應該是這個樣子:

                copy("../new/dir");
                {
                    fileset("src_dir");
                }

            看到差別了嗎? 以上代碼(如果可以在Java中用的化), 是一個特殊的copy算符, 有點像
            for循環或者Java5中的foreach循環。如果我們有一個轉換器, 可以把XML轉換到Java, 大
            概就會得到上面這段事實上不可以執行的代碼。因為Java的技術規范是定死的, 我們沒有
            辦法在程序里改變它。我們可以增加包, 增加類, 增加方法, 但是我們沒辦法增加算符,
            而對于XML, 我們顯然可以任由自己增加這樣的東西。對于XML的語法樹來說, 只要原意,
            我們可以任意增加任何元素, 因此等于我們可以任意增加算符。如果你還不太明白的話,
            看下面這個例子, 加入我們要給Java引入一個unless算符:

                unless(someObject.canFly())
                {
                    someObject.transportByGround():
                }

            在上面的兩個例子中, 我們打算給Java語法擴展兩個算符, 成組拷貝文件算符和條件算符
            unless, 我們要想做到這一點, 就必須修改Java編譯器能夠接受的抽象語法樹, 顯然我們
            無法用Java標準的功能來實現它。但是在XML中我們可以輕而易舉地做到。我們的解析器
            根據 XML元素, 生成抽象語法樹, 由此生成算符, 所以, 我們可以任意引入任何算符。

            對于復雜的算符來說, 這樣做的好處顯而易見。比如, 用特定的算符來做檢出源碼, 編譯
            文件, 單元測試, 發送郵件等任務, 想想看有多么美妙。對于特定的題目, 比如說構造軟
            件項目, 這些算符的使用可以大幅減低少代碼的數量。增加代碼的清晰程度和可重用性。
            解釋性的XML可以很容易的達到這個目標。XML是存儲層次化數據的簡單數據文件, 而在
            Java中, 由于層次結構是定死的(你很快就會看到, Lisp的情況與此截然不同), 我們就沒
            法達到上述目標。也許這正是Ant的成功之處呢。

            你可以注意一下最近Java和C#的變化(尤其是C#3.0的技術規范), C#把常用的功能抽象出
            來, 作為算符增加到C#中。C#新增加的query算符就是一個例子。它用的還是傳統的作法:
            C#的設計者修改抽象語法樹, 然后增加對應的實現。如果程序員自己也能修改抽象語法樹
            該有多好! 那樣我們就可以構造用于特定問題的子語言(比如說就像Ant這種用于構造項目
            的語言), 你能想到別的例子嗎? 再思考一下這個概念。不過也不必思考太甚, 我們待會
            還會回到這個題目。那時候就會更加清晰。

            離Lisp越來越近

            我們先把算符的事情放一放, 考慮一下Ant設計局限之外的東西。我早先說過, Ant可以通
            過寫Java類來擴展。Ant解析器會根據名字來匹配XML元素和Java類, 一旦找到匹配, 就執
            行相應任務。為什么不用Ant自己來擴展Ant呢? 畢竟核心任務要包含很多傳統語言的結構
            (例如"if"), 如果Ant自身就能提供構造任務的能力(而不是依賴java類), 我們就可以得
            到更高的移植性。我們將會依賴一組核心任務(如果你原意, 也不妨把它稱作標準庫), 而
            不用管有沒有Java 環境了。這組核心任務可以用任何方式來實現, 而其他任務建筑在這
            組核心任務之上, 那樣的話, Ant就會成為通用的, 可擴展的, 基于XML的編程語言。考慮
            下面這種代碼的可能性:

                <task name="Test">
                    <echo message="Hello World" />
                </task>
                <Test />

            如果XML支持"task"的創建, 上面這段代碼就會輸出"Hello World!". 實際上, 我們可以
            用Java寫個"task"任務, 然后用Ant-XML來擴展它。Ant可以在簡單原語的基礎上寫出更復
            雜的原語, 就像其他編程語言常用的作法一樣。這也就是我們一開始提到的基于XML的編
            程語言。這樣做用處不大(你知道為甚么嗎?), 但是真的很酷。

            再看一回我們剛才說的Task任務。祝賀你呀, 你在看Lisp代碼!!! 我說什么? 一點都不像
            Lisp嗎? 沒關系, 我們再給它收拾一下。

            比XML更好

            前面一節說過, Ant自我擴展沒什么大用, 原因在于XML很煩瑣。對于數據來說, 這個問題
            還不太大, 但如果代碼很煩瑣的話, 光是打字上的麻煩就足以抵消它的好處。你寫過Ant
            的腳本嗎? 我寫過, 當腳本達到一定復雜度的時候, XML非常讓人厭煩。想想看吧, 為了
            寫結束標簽, 每個詞都得打兩遍, 不發瘋算好的!

            為了解決這個問題, 我們應當簡化寫法。須知, XML僅僅是一種表達層次化數據的方式。
            我們并不是一定要使用尖括號才能得到樹的序列化結果。我們完全可以采用其他的格式。
            其中的一種(剛好就是Lisp所采用的)格式, 叫做s表達式。s表達式要做的和XML一樣, 但
            它的好處是寫法更簡單, 簡單的寫法更適合代碼輸入。后面我會詳細講s表達式。這之前
            我要清理一下XML的東西。考慮一下關于拷貝文件的例子:

                <copy toDir="../new/dir">
                    <fileset dir="src_dir">
                </copy>

            想想看在內存里面, 這段代碼的解析樹在內存會是什么樣子? 會有一個"copy"節點, 其下
            有一個 "fileset"節點, 但是屬性在哪里呢? 它怎樣表達呢? 如果你以前用過XML, 并且
            弄不清楚該用元素還是該用屬性, 你不用感到孤單, 別人一樣糊涂著呢。沒人真的搞得清
            楚。這個選擇與其說是基于技術的理由, 還不如說是閉著眼瞎摸。從概念上來講, 屬性也
            是一種元素, 任何屬性能做的, 元素一樣做得到。XML引入屬性的理由, 其實就是為了讓
            XML寫法不那么冗長。比如我們看個例子:

                <copy>
                    <toDir>../new/dir</toDir>
                    <fileset>
                        <dir>src_dir</dir>
                    </fileset>
                </copy>

            兩下比較, 內容的信息量完全一樣, 用屬性可以減少打字數量。如果XML沒有屬性的話,
            光是打字就夠把人搞瘋掉。

            說完了屬性的問題, 我們再來看一看s表達式。之所以繞這么個彎, 是因為s表達式沒有屬
            性的概念。因為s表達式非常簡練, 根本沒有必要引入屬性。我們在把XML轉換成s表達式
            的時候, 心里應該記住這一點。看個例子, 上面的代碼譯成s表達式是這樣的:

                (copy
                    (todir "../new/dir")
                    (fileset (dir "src_dir")))

            仔細看看這個例子, 差別在哪里? 尖括號改成了圓括號, 每個元素原來是有一對括號標記
            包圍的, 現在取消了后一個(就是帶斜杠的那個)括號標記。表示元素的結束只需要一個")"
            就可以了。不錯, 差別就是這些。這兩種表達方式的轉換, 非常自然, 也非常簡單。s表
            達式打起字來, 也省事得多。第一次看s表達式(Lisp)時, 括號很煩人是吧? 現在我們明
            白了背后的道理, 一下子就變得容易多了。至少, 比XML要好的多。用s表達式寫代碼, 不
            單是實用, 而且也很讓人愉快。s表達式具有XML的一切好處, 這些好處是我們剛剛探討過
            的。現在我們看看更加Lisp風格的task例子:

                (task (name "Test")
                    (echo (message "Hellow World!")))
                (Test)

            用Lisp的行話來講, s表達式稱為表(list)。對于上面的例子, 如果我們寫的時候不加換
            行, 用逗號來代替空格, 那么這個表達式看起來就非常像一個元素列表, 其中又嵌套著其
            他標記。

                (task, (name, "test"), (echo, (message, "Hello World!")))

            XML自然也可以用這樣的風格來寫。當然上面這句并不是一般意義上的元素表。它實際上
            是一個樹。這和XML的作用是一樣的。稱它為列表, 希望你不會感到迷惑, 因為嵌套表和
            樹實際上是一碼事。Lisp的字面意思就是表處理(list processing), 其實也可以稱為樹
            處理, 這和處理XML節點沒有什么不同。

            經受這一番折磨以后, 現在我們終于相當接近Lisp了, Lisp的括號的神秘本質(就像許多
            Lisp狂熱分子認為的)逐漸顯現出來。現在我們繼續研究其他內容。

            重新審視C語言的宏

            到了這里, 對XML的討論你大概都聽累了, 我都講累了。我們先停一停, 把樹, s表達式,
            Ant這些東西先放一放, 我們來說說C的預處理器。一定有人問了, 我們的話題和C有什么
            關系? 我們已經知道了很多關于元編程的事情, 也探討過專門寫代碼的代碼。理解這問題
            有一定難度, 因為相關討論文章所使用的編程語言, 都是你們不熟悉的。但是如果只論概
            念的話, 就相對要簡單一些。我相信, 如果以C語言做例子來討論元編程, 理解起來一定
            會容易得多。好, 我們接著看。

            一個問題是, 為什么要用代碼來寫代碼呢? 在實際的編程中, 怎樣做到這一點呢? 到底元
            編程是什么意思? 你大概已經聽說過這些問題的答案, 但是并不懂得其中緣由。為了揭示
            背后的真理, 我們來看一下一個簡單的數據庫查詢問題。這種題目我們都做過。比方說,
            直接在程序碼里到處寫SQL語句來修改表(table)里的數據, 寫多了就非常煩人。即便用
            C#3.0的LINQ, 仍然不減其痛苦。寫一個完整的SQL查詢(盡管語法很優美)來修改某人的地
            址, 或者查找某人的名字, 絕對是件令程序員倍感乏味的事情, 那么我們該怎樣來解決這
            個問題? 答案就是: 使用數據訪問層。

            概念挺簡單, 其要點是把數據訪問的內容(至少是那些比較瑣碎的部分)抽象出來, 用類來
            映射數據庫的表, 然后用訪問對象屬性訪問器(accessor)的辦法來間接實現查詢。這樣就
            極大地簡化了開發工作量。我們用訪問對象的方法(或者屬性賦值, 這要視你選用的語言
            而定)來代替寫SQL查詢語句。凡是用過這種方法的人, 都知道這很節省時間。當然, 如果
            你要親自寫這樣一個抽象層, 那可是要花非常多的時間的--你要寫一組類來映射表, 把屬
            性訪問轉換為SQL查詢, 這個活相當耗費精力。用手工來做顯然是很不明智的。但是一旦
            你有了方案和模板, 實際上就沒有多少東西需要思考的。你只需要按照同樣的模板一次又
            一次重復編寫相似代碼就可以了。事實上很多人已經發現了更好的方法, 有一些工具可以
            幫助你連接數據庫, 抓取數據庫結構定義(schema), 按照預定義的或者用戶定制的模板來
            自動編寫代碼。

            如果你用過這種工具, 你肯定會對它的神奇效果深為折服。往往只需要鼠標點擊數次, 就
            可以連接到數據庫, 產生數據訪問源碼, 然后把文件加入到你的工程里面, 十幾分鐘的工
            作, 按照往常手工方式來作的話, 也許需要數百個小時人工(man-hours)才能完成。可是,
            如果你的數據庫結構定義后來改變了怎么辦? 那樣的話, 你只需把這個過程重復一遍就可
            以了。甚至有一些工具能自動完成這項變動工作。你只要把它作為工程構造的一部分, 每
            次編譯工程的時候, 數據庫部分也會自動地重新構造。這真的太棒了。你要做的事情基本
            上減到了0。如果數據庫結構定義發生了改變, 并在編譯時自動更新了數據訪問層的代碼,
            那么程序中任何使用過時的舊代碼的地方, 都會引發編譯錯誤。

            數據訪問層是個很好的例子, 這樣的例子還有好多。從GUI樣板代碼, WEB代碼, COM和
            CORBA存根, 以及MFC和ATL等等。在這些地方, 都是有好多相似代碼多次重復。既然這些
            代碼有可能自動編寫, 而程序員時間又遠遠比CPU時間昂貴, 當然就產生了好多工具來自
            動生成樣板代碼。這些工具的本質是什么呢? 它們實際上就是制造程序的程序。它們有一
            個神秘的名字, 叫做元編程。所謂元編程的本義, 就是如此。

            元編程本來可以用到無數多的地方, 但實際上使用的次數卻沒有那么多。歸根結底, 我們
            心里還是在盤算, 假設重復代碼用拷貝粘貼的話, 大概要重復6,7次, 對于這樣的工作量,
            值得專門建立一套生成工具嗎? 當然不值得。數據訪問層和COM存根往往需要重用數百次,
            甚至上千次, 所以用工具生成是最好的辦法。而那些僅僅是重復幾次十幾次的代碼, 是沒
            有必要專門做工具的。不必要的時候也去開發代碼生成工具, 那就顯然過度估計了代碼生
            成的好處。當然, 如果創建這類工具足夠簡單的話, 還是應當盡量多用, 因為這樣做必然
            會節省時間。現在來看一下有沒有合理的辦法來達到這個目的。

            現在, C預處理器要派上用場了。我們都用過C/C++的預處理器, 我們用它執行簡單的編譯
            指令, 來產生簡單的代碼變換(比方說, 設置調試代碼開關), 看一個例子:

                #define triple(X) X+X+X

            這一行的作用是什么? 這是一個簡單的預編譯指令, 它把程序中的triple(X)替換稱為
            X+X+X。例如, 把所有的triple(5)都換成5+5+5, 然后再交給編譯器編譯。這就是一個簡
            單的代碼生成的例子。要是C的預處理器再強大一點, 要是能夠允許連接數據庫, 要是能
            多一些其他簡單的機制, 我們就可以在我們程序的內部開發自己的數據訪問層。下面這個
            例子, 是一個假想的對C宏的擴展:

                #get-db-schema("127.0.0.1")
                #iterate-through-tables
                #for-each-table
                    class #table-name
                        {
                        };
                #end-for-each

            我們連接數據庫結構定義, 遍歷數據表, 然后對每個表創建一個類, 只消幾行代碼就完成
            了這個工作。這樣每次編譯工程的時候, 這些類都會根據數據庫的定義同步更新。顯而易
            見, 我們不費吹灰之力就在程序內部建立了一個完整的數據訪問層, 根本用不著任何外部
            工具。當然這種作法有一個缺點, 那就是我們得學習一套新的"編譯時語言", 另一個缺點
            就是根本不存在這么一個高級版的C預處理器。需要做復雜代碼生成的時候, 這個語言(譯
            者注: 這里指預處理指令, 即作者所說的"編譯時語言")本身也一定會變得相當復雜。它
            必須支持足夠多的庫和語言結構。比如說我們想要生成的代碼要依賴某些ftp服務器上的
            文件, 預處理器就得支持ftp訪問, 僅僅因為這個任務而不得不創造和學習一門新的語言,
            真是有點讓人惡心(事實上已經存在著有此能力的語言, 這樣做就更顯荒謬)。我們不妨再
            靈活一點, 為什么不直接用 C/C++自己作為自己的預處理語言呢?  這樣子的話, 我們可
            以發揮語言的強大能力, 要學的新東西也只不過是幾個簡單的指示字 , 這些指示字用來
            區別編譯時代碼和運行時代碼。

                <%
                    cout<<"Enter a number: ";
                    cin>>n;
                %>
                for(int i=0;i< <% n %>;i++)
                {
                    cout<<"hello"<<endl;
                }

            你明白了嗎? 在<%和%>標記之間的代碼是在編譯時運行的, 標記之外的其他代碼都是普通
            代碼。編譯程序時, 系統會提示你輸入一個數, 這個數在后面的循環中會用到。而for循
            環的代碼會被編譯。假定你在編譯時輸入5, for循環的代碼將會是:

                for(int i=0;i<5; i++)
                {
                    cout<<"hello"<<endl;
                }

            又簡單又有效率, 也不需要另外的預處理語言。我們可以在編譯時就充分發揮宿主語言(
            此處是C/C++)的強大能力, 我們可以很容易地在編譯時連接數據庫, 建立數據訪問層, 就
            像JSP或者ASP創建網頁那樣。我們也用不著專門的窗口工具來另外建立工程。我們可以在
            代碼中立即加入必要的工具。我們也用不著顧慮建立這種工具是不是值得, 因為這太容易
            了, 太簡單了。這樣子不知可以節省多少時間啊。

            你好, Lisp

            到此刻為止, 我們所知的關于Lisp的指示可以總結為一句話: Lisp是一個可執行的語法更
            優美的XML, 但我們還沒有說Lisp是怎樣做到這一點的, 現在開始補上這個話題。

            Lisp有豐富的內置數據類型, 其中的整數和字符串和其他語言沒什么分別。像71或者
            "hello"這樣的值, 含義也和C++或者Java這樣的語言大體相同。真正有意思的三種類型是
            符號(symbol), 表和函數。這一章的剩余部分, 我都會用來介紹這幾種類型, 還要介紹
            Lisp環境是怎樣編譯和運行源碼的。這個過程用Lisp的術語來說通常叫做求值。通讀這一
            節內容, 對于透徹理解元編程的真正潛力, 以及代碼和數據的同一性, 和面向領域語言的
            觀念, 都極其重要。萬勿等閑視之。我會盡量講得生動有趣一些, 也希望你能獲得一些
            啟發。那好, 我們先講符號。

            大體上, 符號相當于C++或Java語言中的標志符, 它的名字可以用來訪問變量值(例如
            currentTime, arrayCount, n, 等等), 差別在于, Lisp中的符號更加基本。在C++或
            Java里面, 變量名只能用字母和下劃線的組合, 而Lisp的符號則非常有包容性, 比如, 加
            號(+)就是一個合法的符號, 其他的像-, =, hello-world, *等等都可以是符號名。符號
            名的命名規則可以在網上查到。你可以給這些符號任意賦值, 我們這里先用偽碼來說明這
            一點。假定函數set是給變量賦值(就像等號=在C++和Java里的作用), 下面是我們的例子:

                set(test, 5)            // 符號test的值為5
                set(=, 5)               // 符號=的值為5
                set(test, "hello")      // 符號test的值為字符串"hello"
                set(test, =)            // 此時符號=的值為5, 所以test的也為5
                set(*, "hello")         // 符號*的值為"hello"

            好像有什么不對的地方? 假定我們對*賦給整數或者字符串值, 那做乘法時怎么辦? 不管
            怎么說, *總是乘法呀? 答案簡單極了。Lisp中函數的角色十分特殊, 函數也是一種數據
            類型, 就像整數和字符串一樣, 因此可以把它賦值給符號。乘法函數Lisp的內置函數, 默
            認賦給*, 你可以把其他函數賦值給*, 那樣*就不代表乘法了。你也可以把這函數的值存
            到另外的變量里。我們再用偽碼來說明一下:

                *(3,4)          // 3乘4, 結果是12
                set(temp, *)    // 把*的值, 也就是乘法函數, 賦值給temp
                set(*, 3)       // 把3賦予*
                *(3,4)          // 錯誤的表達式, *不再是乘法, 而是數值3
                temp(3,4)       // temp是乘法函數, 所以此表達式的值為3乘4等于12
                set(*, temp)    // 再次把乘法函數賦予*
                *(3,4)          // 3乘4等于12

            再古怪一點, 把減號的值賦給加號:

                set(+, -)       // 減號(-)是內置的減法函數
                +(5, 4)         // 加號(+)現在是代表減法函數, 結果是5減4等于1

            這只是舉例子, 我還沒有詳細講函數。Lisp中的函數是一種數據類型, 和整數, 字符串,
            符號等等一樣。一個函數并不必然有一個名字, 這和C++或者Java語言的情形很不相同。
            在這里函數自己代表自己。事實上它是一個指向代碼塊的指針, 附帶有一些其他信息(例
            如一組參數變量)。只有在把函數賦予其他符號時, 它才具有了名字, 就像把一個數值或
            字符串賦予變量一樣的道理。你可以用一個內置的專門用于創建函數的函數來創建函數,
            然后把它賦值給符號fn, 用偽碼來表示就是:

                fn [a]
                {
                    return *(a, 2);
                }

            這段代碼返回一個具有一個參數的函數, 函數的功能是計算參數乘2的結果。這個函數還
            沒有名字, 你可以把此函數賦值給別的符號:

                set(times-two, fn [a] {return *(a, 2)})

            我們現在可以這樣調用這個函數:

                time-two(5)         // 返回10

            我們先跳過符號和函數, 講一講表。什么是表? 你也許已經聽過好多相關的說法。表, 一
            言以蔽之, 就是把類似XML那樣的數據塊, 用s表達式來表示。表用一對括號括住, 表中元
            素以空格分隔, 表可以嵌套。例如(這回我們用真正的Lisp語法, 注意用分號表示注釋):

                ()                      ; 空表
                (1)                     ; 含一個元素的表
                (1 "test")              ; 兩元素表, 一個元素是整數1, 另一個是字符串
                (test "hello")          ; 兩元素表, 一個元素是符號, 另一個是字符串
                (test (1 2) "hello")    ; 三元素表, 一個符號test, 一個含有兩個元素1和2的
                                        ; 表, 最后一個元素是字符串

            當Lisp系統遇到這樣的表時, 它所做的, 和Ant處理XML數據所做的, 非常相似, 那就是試
            圖執行它們。其實, Lisp源碼就是特定的一種表, 好比Ant源碼是一種特定的XML一樣。
            Lisp執行表的順序是這樣的, 表的第一個元素當作函數, 其他元素當作函數的參數。如果
            其中某個參數也是表, 那就按照同樣的原則對這個表求值, 結果再傳遞給最初的函數作為
            參數。這就是基本原則。我們看一下真正的代碼:

                (* 3 4)                 ; 相當于前面列舉過的偽碼*(3,4), 即計算3乘4
                (times-two 5)           ; 返回10, times-two按照前面的定義是求參數的2倍
                (3 4)                   ; 錯誤, 3不是函數
                (time-two)              ; 錯誤, times-two要求一個參數
                (times-two 3 4)         ; 錯誤, times-two只要求一個參數
                (set + -)               ; 把減法函數賦予符號+
                (+ 5 4)                 ; 依據上一句的結果, 此時+表示減法, 所以返回1
                (* 3 (+ 2 2))           ; 2+2的結果是4, 再乘3, 結果是12

            上述的例子中, 所有的表都是當作代碼來處理的。怎樣把表當作數據來處理呢? 同樣的,
            設想一下, Ant是把XML數據當作自己的參數。在Lisp中, 我們給表加一個前綴'來表示數
            據。

                (set test '(1 2))       ; test的值為兩元素表
                (set test (1 2))        ; 錯誤, 1不是函數
                (set test '(* 3 4))     ; test的值是三元素表, 三個元素分別是*, 3, 4

            我們可以用一個內置的函數head來返回表的第一個元素, tail函數來返回剩余元素組成的
            表。

                (head '(* 3 4))         ; 返回符號*
                (tail '(* 3 4))         ; 返回表(3 4)
                (head (tal '(* 3 4)))   ; 返回3
                (head test)             ; 返回*

            你可以把Lisp的內置函數想像成Ant的任務。差別在于, 我們不用在另外的語言中擴展
            Lisp(雖然完全可以做得到), 我們可以用Lisp自己來擴展自己, 就像上面舉的times-two
            函數的例子。Lisp的內置函數集十分精簡, 只包含了十分必要的部分。剩下的函數都是作
            為標準庫來實現的。

            Lisp宏

            我們已經看到, 元編程在一個類似jsp的模板引擎方面的應用。我們通過簡單的字符串處
            理來生成代碼。但是我們可以做的更好。我們先提一個問題, 怎樣寫一個工具, 通過查找
            目錄結構中的源文件來自動生成Ant腳本。

            用字符串處理的方式生成Ant腳本是一種簡單的方式。當然, 還有一種更加抽象, 表達能
            力更強, 擴展性更好的方式, 就是利用XML庫在內存中直接生成XML節點, 這樣的話內存中
            的節點就可以自動序列化成為字符串。不僅如此, 我們的工具還可以分析這些節點, 對已
            有的XML文件做變換。通過直接處理XML節點。我們可以超越字符串處理, 使用更高層次的
            概念, 因此我們的工作就會做的更快更好。

            我們當然可以直接用Ant自身來處理XML變換和制作代碼生成工具。或者我們也可以用Lisp
            來做這項工作。正像我們以前所知的, 表是Lisp內置的數據結構, Lisp含有大量的工具來
            快速有效的操作表(head和tail是最簡單的兩個)。而且, Lisp沒有語義約束, 你可以構造
            任何數據結構, 只要你原意。

            Lisp通過宏(macro)來做元編程。我們寫一組宏來把任務列表(to-do list)轉換為專用領
            域語言。

            回想一下上面to-do list的例子, 其XML的數據格式是這樣的:

                <todo name = "housework">
                    <item priority = "high">Clean the hose</item>
                    <item priority = "medium">Wash the dishes</item>
                    <item priority = "medium">Buy more soap</item>
                </todo>

            相應的s表達式是這樣的:

                (todo "housework"
                    (item (priority high) "Clean the house")
                    (item (priority medium) "Wash the dishes")
                    (item (priority medium) "Buy more soap"))

            假設我們要寫一個任務表的管理程序, 把任務表數據存到一組文件里, 當程序啟動時, 從
            文件讀取這些數據并顯示給用戶。在別的語言里(比如說Java), 這個任務該怎么做? 我們
            會解析XML文件, 從中得出任務表數據, 然后寫代碼遍歷XML樹, 再轉換為Java的數據結構
            (老實講, 在Java里解析XML真不是件輕松的事情), 最后再把數據展示給用戶。現在如果
            用Lisp, 該怎么做?

            假定要用同樣思路的化, 我們大概會用Lisp庫來解析XML。XML對我們來說就是一個Lisp
            的表(s表達式), 我們可以遍歷這個表, 然后把相關數據提交給用戶。可是, 既然我們用
            Lisp, 就根本沒有必要再用XML格式保存數據, 直接用s表達式就好了, 這樣就沒有必要做
            轉換了。我們也用不著專門的解析庫, Lisp可以直接在內存里處理s表達式。注意, Lisp
            編譯器和.net編譯器一樣, 對Lisp程序來說, 在運行時總是隨時可用的。

            但是還有更好的辦法。我們甚至不用寫表達式來存儲數據, 我們可以寫宏, 把數據當作代
            碼來處理。那該怎么做呢? 真的簡單。回想一下, Lisp的函數調用格式:

                (function-name arg1 arg2 arg3)

            其中每個參數都是s表達式, 求值以后, 傳遞給函數。如果我們用(+ 4 5)來代替arg1,
            那么, 程序會先求出結果, 就是9, 然后把9傳遞給函數。宏的工作方式和函數類似。主要
            的差別是, 宏的參數在代入時不求值。

                (macro-name (+ 4 5))

            這里, (+ 4 5)作為一個表傳遞給宏, 然后宏就可以任意處理這個表, 當然也可以對它求
            值。宏的返回值是一個表, 然后有程序作為代碼來執行。宏所占的位置, 就被替換為這個
            結果代碼。我們可以定義一個宏把數據替換為任意代碼, 比方說, 替換為顯示數據給用戶
            的代碼。

            這和元編程, 以及我們要做的任務表程序有什么關系呢? 實際上, 編譯器會替我們工作,
            調用相應的宏。我們所要做的, 僅僅是創建一個把數據轉換為適當代碼的宏。

            例如, 上面曾經將過的C的求三次方的宏, 用Lisp來寫是這樣子:

                (defmacro triple (x)
                    `(+ ~x ~x ~x))

            (譯注: 在Common Lisp中, 此處的單引號應當是反單引號, 意思是對表不求值, 但可以對
            表中某元素求值, 記號~表示對元素x求值, 這個求值記號在Common Lisp中應當是逗號。
            反單引號和單引號的區別是, 單引號標識的表, 其中的元素都不求值。這里作者所用的記
            號是自己發明的一種Lisp方言Blaise, 和common lisp略有不同, 事實上, 發明方言是
            lisp高手獨有的樂趣, 很多狂熱分子都熱衷這樣做。比如Paul Graham就發明了ARC, 許多
            記號比傳統的Lisp簡潔得多, 顯得比較現代)

            單引號的用處是禁止對表求值。每次程序中出現triple的時候,

                (triple 4)

            都會被替換成:

                (+ 4 4 4)

            我們可以為任務表程序寫一個宏, 把任務數據轉換為可執行碼, 然后執行。假定我們的輸
            出是在控制臺:

                (defmacro item (priority note)
                    `(block
                        (print stdout tab "Prority: " ~(head (tail priority)) endl)
                        (print stdout tab "Note: " ~note endl endl)))

            我們創造了一個非常小的有限的語言來管理嵌在Lisp中的任務表。這個語言只用來解決特
            定領域的問題, 通常稱之為DSLs(特定領域語言, 或專用領域語言)。

            特定領域語言

            本文談到了兩個特定領域語言, 一個是Ant, 處理軟件構造。一個是沒起名字的, 用于處
            理任務表。兩者的差別在于, Ant是用XML, XML解析器, 以及Java語言合在一起構造出來
            的。而我們的迷你語言則完全內嵌在Lisp中, 只消幾分鐘就做出來了。

            我們已經說過了DSL的好處, 這也就是Ant用XML而不直接用Java的原因。如果使用Lisp,
            我們可以任意創建DSL, 只要我們需要。我們可以創建用于網站程序的DSL, 可以寫多用戶
            游戲, 做固定收益貿易(fixed income trade), 解決蛋白質折疊問題, 處理事務問題, 等
            等。我們可以把這些疊放在一起, 造出一個語言, 專門解決基于網絡的貿易程序, 既有網
            絡語言的優勢, 又有貿易語言的好處。每天我們都會收獲這種方法帶給我們的益處, 遠遠
            超過Ant所能給予我們的。

            用DSL解決問題, 做出的程序精簡, 易于維護, 富有彈性。在Java里面, 我們可以用類來
            處理問題。這兩種方法的差別在于, Lisp使我們達到了一個更高層次的抽象, 我們不再受
            語言解析器本身的限制, 比較一下用Java庫直接寫的構造腳本和用Ant寫的構造腳本其間
            的差別。同樣的, 比較一下你以前所做的工作, 你就會明白Lisp帶來的好處。

            接下來

            學習Lisp就像戰爭中爭奪山頭。盡管在電腦科學領域, Lisp已經算是一門古老的語言, 直
            到現在仍然很少有人真的明白該怎樣給初學者講授Lisp。盡管Lisp老手們盡了很大努力,
            今天新手學習Lisp仍然是困難重重。好在現在事情正在發生變化, Lisp的資源正在迅速增
            加, 隨著時間推移, Lisp將會越來越受關注。

            Lisp使人超越平庸, 走到前沿。學會Lisp意味著你能找到更好的工作, 因為聰明的雇主會
            被你與眾不同的洞察力所打動。學會Lisp也可能意味著明天你可能會被解雇, 因為你總是
            強調, 如果公司所有軟件都用Lisp寫, 公司將會如何卓越, 而這些話你的同事會聽煩的。
            Lisp值得努力學習嗎? 那些已經學會Lisp的人都說值得, 當然, 這取決于你的判斷。

            你的看法呢?

            這篇文章寫寫停停, 用了幾個月才最終完成。如果你覺得有趣, 或者有什么問題, 意見或
            建議, 請給我發郵件coffeemug@gmail.com, 我會很高興收到你的反饋。

            posted @ 2011-02-15 09:29 Vincent 閱讀(813) | 評論 (3)編輯 收藏

            2010年12月16日 #

            互斥鎖與條件變量的語義

            互斥鎖與條件變量的語義


            互斥鎖,我要對一塊共享數據操作,但是我怕同時你也操作,那就亂套了,所以我要加鎖,這個時候我就開始操作這塊共享數據,而你進不了臨界區,等我操作完了,把鎖丟掉,你就可以拿到鎖進去操作了

             

            條件變量,我要看一塊共享數據里某一個條件是否達成,我很關心這個,如果我用互斥鎖,不停的進入臨界區看條件是否達成,這簡直太悲劇了,這樣一來,我醒的時候會占CPU資源,但是卻干不了什么時,只是頻繁的看條件是否達成,而且這對別人來說也是一種損失,我每次加上鎖,別人就進不了臨界區干不了事了。好吧,輪詢總是痛苦的,咱等別人通知吧,于是條件變量出現了,我依舊要拿個鎖,進了臨界區,看到了共享數據,發現,咦,條件還不到,于是我就調用pthread_cond_wait(),先把鎖丟了,好讓別人可以去對共享數據做操作,然后呢?然后我就睡了,直到特定的條件發生,別人修改完了共享數據,給我發了個消息,我又重新拿到了鎖,繼續干俺要干的事情了……

             

            posted @ 2010-12-16 15:35 Vincent 閱讀(849) | 評論 (0)編輯 收藏

            線程內幕

            一.
            在主線程中調用
            (1)pthread_create( &thread_a, NULL, thread_function, NULL);
            (2)pthread_create( &thread_b, NULL, thread_function, NULL);
            (3)pthread_create( &thread_c, NULL, thread_function, NULL);

             


            在段2處,線程b可以認為線程a已經存在
            但是在段2執行完以后,主線程并不知道線程a和線程b誰先執行,并不能在這里做線程a先于線程b執行的假設
            因為線程的時間片分配在這里是未知的

             

            二.
            myglobal=myglobal+1;
            myglobal是全局變量,多個線程同時在做累加的工作
            是否應該為myglobal=myglobal+1;加鎖呢?
            肯定是應該加鎖
            首先我們并不知道myglobal=myglobal+1;又或是++ myglobal;能否被編譯成一條匯編指令
            就算如此++ myglobal被編譯成了原子操作
            但考慮到多核處理器,其原子操作可能在多CPU上同時處理
            其結果仍然是不可預估的


            以上內容轉述自http://www.ibm.com/developerworks/cn/linux/thread/posix_thread2/index.html

            posted @ 2010-12-16 14:37 Vincent 閱讀(485) | 評論 (0)編輯 收藏

            第4章

            UNIX網絡編程筆記

            做下筆記,以供不時之需:)

            4

             

             

            socket(int family,               int type,                      int protocol);

            非負描述字-成功,-1-出錯

                        協議族                   套接口類型                              協議類型常量值

                        AF_INET  IPv4協議 SOCK_STREAM 字節流           IPPROTO_TCP  TCP傳輸協議

                        AF_INET6 IPv6協議    SOCK_DGRAM  數據報              IPPROTO_UDP  UDP傳輸協議

                       AF_LOCAL UNIX域協議  SOCK_SEQPACKET有序分組          IPPROTO_SCTP SCTP傳輸協議

                        AF_ROUTE 路由套接口  SOCK_RAW    原始套接口

                        AF_KEY   密鑰套接口

             

             

             

            protocol可以設為0,以選擇所給定的familytype組合的系統缺省值

             

             

            connect(int sockfd,           const struct sockaddr *servaddr,    socklen_t addrlen);

                     要連接的套接口描述字         指向描述連接地址的套接口地址的結構指針     指向套接口地址的結構大小        

            0-成功,-1-失敗

             

             

            ETIMEDOUT錯誤,客戶端未收到SYN分節的響應

            4.4BSD內核中SYN分節會在首次發出后的6秒,24秒后再發出SYN分節,如果75秒后仍無響應,返回錯誤(TCPv2 p828)

             

            ECONNERFUSED錯誤,客戶端收到RST分節時就返回錯誤

            其中產生RST的三個條件是:

            1.目的地為某端口的SYN到達,然而該端口上沒有正在監聽的服務器

            2.TCP想取消一個已有連接

            3.TCP接受到一個根本不存在的連接上的分節(TCPv1 246-250)

             

            EHOSTUNREACHENETUNREACH錯誤

            在發出SYN分節的中間某個路由器引發了目的地不可達的ICMP錯誤,客戶主機保存該消息,但仍然繼續發送SYN,直到75秒后,如果仍未有回應,則把錯誤消息返回給進程。

            以下情況也會產生此類錯誤

            1.本地系統的轉發表,根本沒有到達遠地系統的路徑

            2.connect調用根本不等待就返回(非阻塞?)

             

            :connect失敗則該套接口不再可用,必須關閉。

             

             

            bind(int sockfd,                  const struct sockaddr *myaddr,               socklen_t addrlen);

            0-成功,-1失敗

                 要綁定的套接口描述字       指向描述要綁定的套接口地址的結構指針         指向套接口地址的結構大小

             

            如果一個TCP客戶端或服務器未曾調用bind捆綁一個端口,當調用connectlisten時,內核會為相應的套接口選擇一個臨時端口

            進程可把特定IP地址捆綁到它的套接口上,這個特定IP必須是主機的網絡接口之一。對于TCP客戶,這就限定了套接口只接受目的地為這個特定IP的客戶連接。

            TCP客戶通過不把IP捆綁到它的套接口上,內核會根據所用外出網絡接口來選擇源IP地址,而所用外出的接口則取決于到達服務器所需的路徑。(TCPv2 p737)

            如果TCP服務器沒有把IP地址捆綁到它的套接口上,內核就把客戶發送的SYN的宿IP地址作為服務器的源地址。(TCPv2 p943)

             

            EADDRINUSE錯誤 地址已使用

             

             

             

            listen(   int sockfd ,                         int backlog);

            0-成功,-1失敗

                  要轉換成被動的套接口     排隊的最大已連接個數

             

             

            1.未完成連接隊列

            處于SYN_RCVD狀態的套接口隊列

            2.已完成連接隊列

            處于ESTABLISHED狀態的套接口隊列

             

            在隊列滿時,服務器如果收到SYN分節,會忽略掉,因為隊列滿是暫時性的,忽略掉可以以期待客戶端的再次連接。而如果返回RST分節,會時客戶端放棄連接。

             

            在三次握手完成之后,但在服務器調用accept之前到達的數據,應有服務器TCP排隊,最大數據量為相應的已連接套接口的接受緩沖區大小。



            Int accept(        int sockfd,                  struct sockaddr* cliaddr,                             socklen_t* addrlen);

            非負描述字-成功,-1-失敗

                                        被動的監聽套接口  對端的套接口地址的結構         對端的套接口地址的結構大小

             

             

            Int close(int sockfd);

            0-       成功,-1-失敗

            要關閉的套接口

             

            缺省行為是修改套接口標記為已關閉,函數會立即返回,此時該套接口描述字已不能再由進程使用。而TCP將嘗試發送已排隊等待發送到對端的任何數據,發送完畢后開始正常的TCP連接終止序列。

             

            SO_LINGER套接口選項可以改變此缺省行為。

            posted @ 2010-12-16 11:39 Vincent 閱讀(353) | 評論 (0)編輯 收藏

            2010年12月12日 #

            懂得

            第一次周六周日在加班
            第一次擔起責任
            第一次怕自己不行
            第一次有那么一點理解到底什么是主程
            這一天僅作紀念:)

            posted @ 2010-12-12 21:55 Vincent 閱讀(356) | 評論 (0)編輯 收藏

            2010年10月19日 #

            游戲服務端程序員群

            群號:16043631 已經250人了 還差250
            幫朋友宣傳一下:)

            posted @ 2010-10-19 16:35 Vincent 閱讀(1820) | 評論 (0)編輯 收藏

            2010年10月14日 #

            [轉]《深度探索C++對象模型》讀書筆記[二]

            2002-7-6

            3.3 Data Member的存取
            1.   不管什么情況,每一個static data member只有一個實體,放在程序的data segment之中,每次程序取用static member,不管是通過operator::還是member selection operator,都會被內部轉化為對該唯一extern實體的直接參考操作。每一個static member的存取以及與class的關聯不會導致任何執行時間或空間上的額外負擔。如果有兩個classes,每一個都聲明了一個static member freeList,那么當它們都放在程序的data segment時,就會導致名稱沖突,編譯器的解決方法是使用name-mangling,暗中對每一個static data member編碼,以獲得一個獨一無二的程序識別代碼。

            2.   有多少個編譯器,就有多少種name-mangling做法,任何name-mangling做法都有兩個要點:

            ü          一種算法,推導出獨一無二的名稱;

            ü          如果編譯系統或者環境工具必須和使用者交談,那些獨一無二的名稱可被輕易推導回原先的名稱。

            3.   取一個static data member的地址,會得到一個指向其數據類型的常量指針,而不是指向其class member的指針。

            4.   nonstatic data members直接放在每一個class object之中,除非經過顯示的explicit或隱含的implicit class object,沒有辦法直接存取它們。只要程序員在一個member function中直接處理一個nonstatic data member,所謂implicit class object就會發生,其實質是編譯器會為這個member function增添一個const this指針,而在函數體內通過這個this指針來存取nontatic data member。

            5.   欲對一個nonstatic data member進行存取操作,編譯器需要把class object的起始地址加上data member的編譯量offset,如地址&someObject.someMember等于&someobject + (&theClass::someMember – 1);指向data member的指針,其offset值總是會被加上1,這樣可以使編譯系統區分出一個指向class第一個data member的指針和一個沒有指向任何data member的指針。

            6.   每一個nonstatic data member的偏移量在編譯時期即可獲知,甚至如果member屬于一個單一或多重繼承體系中base class subobject也是一樣,因此其存取效率和一個C struct member或一個nonderived class的member的存取效率是一樣的。但是在虛擬繼承的情況下就另當別論了:如果該nonstatic data member是一個virtual base class的member,并且通過指針來存取的話,在編譯時期就不會得知這個member真正的offset位置,所以這個存取操作必須延遲至執行期,經由一個額外的間接導引才能夠解決。

            2002-7-7

            3.4 “繼承”與Data Member
            1.   在C++繼承模型中,一個derived class object所表現出來的東西,是其自己的members加上其base classes members的總和。C++并未規定derived class members和base classes members的排列次序。不過,在大部分編譯器上,除virtual base class外,base class members總是先出現。

            2.   一般而言,具體繼承concrete inheritance并不會增加空間或存取時間上的額外負擔。

            3.   把兩個原本獨立不相干的classes湊成一對type/subtype,并帶有繼承關系容易犯兩個錯誤。一是可能會重復設計一些相同操作的函數,一般而言,選擇某些函數做成inline函數,是設計class的一個重要課題;二是把一個class分解為多層,有可能會為了表現class體系之抽象化,因為編譯器的邊界調整而膨脹所需空間。其根本原因是C++保證出現在derived class中的base class subobject有其完整原樣性。

            4.   C++最初問世時,許多編譯器把vptr放在class object的尾端,這樣可以保留base class C struct的對象布局。此后,某些編譯器開始把vptr放在class object的開始處,這樣會給多重繼承下通過指向class members之指針調用virtual function帶來一些幫助,否則,在執行期不僅必須備妥從class object起點處開始量起的offset,而且必須備妥class vptr之間的offset。

            5.   單一繼承提供了一種自然多態的形態,是關于class體系中base type和derived type之間的轉換。一般來說,base class和derived class objects都是從相同的地址開始。但若將vptr放在class object的起始處,如果base class沒有virtual function而derived class有,那么單一繼承的自然多態就會打破。此時,把一個derived object轉換為其base類型就需要編譯器的介入,用以調整地址。而在既是多重繼承又是虛擬繼承的情況下,編譯器的介入則更有必要。

            6.   多重繼承的復雜度在于derived class和其上一個base class乃至上上一個base class之間的非自然關系,其主要問題發生在derived class objects和其第二或后繼的base class objects之間的轉換。對一個多重派生對象,將其地址指定給最左端base class的指針,情況將和單一繼承相同,而第二個或后繼的base class的地址指定操作則需要修改地址,加上或減去(若是downcast)介于中間的base class subobjects的大小。C++并未要求多重繼承時derived class object中各個base class subjectes的排列次序,目前各個編譯器都是根據聲明次序來排列它們。

            7.   class內如果內含一個或多個virtual bass class subobjects,將被分割為兩部分:一個不變局部和一個共享局部。不變局部總是擁有固定的offset,其數據用以指定共享局部的位置,可以直接存取;而共享局部表現的就是virtual base class subobject,其位置會因為每次的派生操作而變化,只可間接存取。各家編譯器實現技術之間的差異就在于間接存取的方法不同。

            8.   一般而言,virtual base class最有效的一種運用方式是:一個沒有任何data member的抽象class。

            2002-7-14

            3.5 對象成員的效率
            如果沒有把優化開關打開,就很難猜測一個程序的效率表現,因為程序代碼潛在性的受到某些與編譯器有關的東西的影響。程序員如果關心效率,應該實際測試,不要光憑推論或常識判斷或假設。優化操作并不一定總是能夠有效運行。

            2002-7-15

            3.6 指向Data Members的指針
            指向data members的指針可用來詳細調查class members的底層布局,可用來決定vptr是放在class的起始處還是尾端,還可用來決定class中access sections的次序。

            取一個nonstatic data member的地址,將會得到它在class的offset;而取一個static data member的地址或者取一個綁定于真正class object身上的data member的地址,將會得到該member在內存中的真正地址。這也正是someType someClass::*和someTye *潛在的區別。

            2002-7-16

            Function語意學 The Semantics of Function
            C++支持三種類型的member functions:static、nonstatic和virtual,每一種類型的調用方式都不同。

            4.1 Members的各種調用方式
            1.   C++的設計準則之一便是nonstatic member function至少必須和一般的nonmember function有著相同的效率。編譯器內部會將member函數實體轉換為對等的nonmember函數實體,其步驟為:

            ü          改寫函數原型signature以安插一個額外的參數this到member function中,使得class object可以調用該函數。其中,this是const指針,若該函數為const,則反映在this上面的結果是this指向的data也為const;

            ü          將每一個對nonstatic data member的存取操作改為經由this指針來存取;

            ü          將member function重新寫成一個外部函數,對函數名稱進行mangling處理;

            此后,每一個函數調用操作也都必須轉換,用以提供相應的實參。

            2.   關于虛擬函數的內部轉換步驟:若normalize是一個virtual member function,ptr->normalize();會被內部轉化為(*ptr->vptr[t])(ptr); 事實上,vptr名稱也會被mangled,因為可能存在有多個vptrs;t是vitrual table slot的索引值,關聯到normalize函數;第二個ptr表示this指針。

            3.   使用class scope operator明確調用一個vitual function,或經由一個class object調用一個vitual function其決議方式會和nontatic member function一樣!故virtual function的一個inline函數實體可被擴展開來,因而提供極大的效率利益。

            4.   static member function的主要特征是沒有this指針,這導致它不能直接存取其class中的nonstatic members,不能被聲明為const、volatile或virtual,也不需要經由class object才能調用。static member function會被提出于class聲明之外,并給予一個經過mangled的適當名稱。如果取一個static member function的地址,得到的將是其在內存中的地址,其地址類型并不是一個指向class member function的指針,而是一個nonmember函數指針。static member function的一個意想不到的好處是可以成為一個callback函數,也可以成功地應用在thread函數身上。

            2002-07-17

            4.2 Virtual Member Functions虛擬成員函數
            1.   C++中,多態polymorphism表示以一個public base class指針或reference尋址出一個derived class object。識別一個class是否支持多態,唯一適當的方法試看它是否有任何virtual function。只要class擁有一個virtual function,它就需要一份額外的執行期型別判斷信息。

            2.   一個class只會有一個virtual table,其中內含對應class object中所有的active virtual functions的函數實體的地址。這些active virtual functions包括:

            ü          一個class定義的函數實體。它會改寫overriding一個可能存在的base class virtual function。

            ü          繼承自base class的函數實體。此時該class不改寫base class virtual function。

            ü          一個pure_virtual_called()函數實體,它既可以扮演pure virtual function的空間保衛者,也可以當作執行期異常處理函數。如果該函數被調用,通常的操作是結束程序。

            3.   每一個virtual function都被指派一個固定不變的索引值,該值在整個繼承體系中保持與特定virtual function的關聯。這樣就可以在編譯時期設定virtual function的調用。

            2002-7-20

            4.   多重繼承下,一個上層basse classes數目為n的derived class,它將內含n-1個額外的virtual tables。其主要實體與最左端的base class共享,其中包含所有virtual functios的地址;n-1個次要實體與其它base classes有關,其中只包含出現在對應base class中virtual functions的地址。

            5.   在多重繼承中支持virtual function,其復雜度圍繞在第二個及后繼base class上,以及執行期this指針調整上。第二(或后繼)base class會影響對virtual function支持的3種情況:

            ü          通過指向第二個base class的指針,調用derived class virtual function;

            ü          通過指向derived class的指針,調用第二個base class中一個繼承而來的virtual function;

            ü          允許virtual function函數的返回值類型有所變化,可能是base type,也可能是publicly derived type。

            6.   關于執行期this指針調整比較有效率的解決方法是thunk。所謂thunk是一小端assembly碼,用來以適當的offset值來調整this指針并跳到相應的virtual function。thunk技術允許virtual table slot繼續內含一個簡單的指針,此時多重繼承將不需要任何空間上的額外負擔!slots中的地址可以直接指向virtual function,也可以指向一個相關的thunk。

            4.3 函數的效能
            nonmember、static member和nonstatic member function在內部都會轉化為完全相同的形式,三者效率相同。

            2002-08-08

            4.4 指向Member Function的指針
            對一個nonstatic member function取址,得到的是該函數在內存中的地址;而面對一個virtual function,得到的將是一個索引值。這個值是不完整的,必須被綁定于一個class object上,才能夠通過它調用函數。指向member function的指針的聲明語法,以及指向member selection運算符的指針,其作用是作為this指針的空間保留者。因此,static member function的類型是函數指針,而不是指向member function的指針。

            使用一個member function指針,如果并不用于virtual function、多重繼承、virtual base class等情況的話,其成本并不比使用一個nonmember function指針要高。

            4.5 Inline Functions
            關鍵詞inline只是一項請求。如果在某個層次上,函數的執行成本比一般的函數調用及返回機制所帶來的負荷低,那么該請求被接受,編譯器就用一個表達式合理地將函數擴展開來。真正的inline函數擴展操作是在函數調用的那一點上。在inline擴展期間,每一個形式參數會被對應的實際參數所取代,inline函數中的每一個局部變量都必須被放在函數調用的一個封閉區段中,并擁有一個獨一無二的名稱。這會帶來參數的求值操作以及臨時性對象的管理。

            2002-08-11

            構造、解構、拷貝語意學  Semantics of Construction, Destruction, and Copy
            1.   一般而言,class的data member應該被初始化,而且只在constructor中或其它member functions中初始化,其它任何操作都將破壞其封裝性質,使其維護和修改更加困難。

            2.   可以定義并調用invoke一個pure virtual function,但它只能被靜態調用,不能經由虛擬機制調用。每一個derived class destructor會被編譯器加以擴展,靜態調用每一個virtual base class以及上一層base class的destructor。因此,不管base class的virtual destructor是否聲明為pure,它必須被定義。

            5.1 無繼承情況下的對象構造
            C++ Standard要求編譯器盡量延遲nontrivial members的實際合成操作,直到真正遇到其使用場所為止。

            5.2 繼承體系下的對象構造
            一般而言,繼承體系下編譯器對constructor所作的擴充操作以及次序大約如下:

            ü          所有virtual base class constructors必須從左到右、從深到淺被調用:如果class被列于member initialization list中,那么任何明確指定的參數都必須傳遞過去,否則如果class有一個default constructor,也應該調用它;class中的每一個virtual base class subobject的偏移量offset必須在執行期可被存取;如果class object是最底層most-derived的class,其constructors可能被調用,某些用以支持這個行為的機制必須被方進來。

            ü          以base class的聲明次序調用上一層base class constructors:如果base class被列于member initialization list中,那么任何明確指定的參數都必須傳遞過去,否則若它有default constructor或default memberwise copy constructor,那么就調用它;如果base class是多重繼承下的第二或后繼的base class,那么this指針必須有所調整。

            ü          如果class object有virtual table pointer(s),它(們)必須被設定初值,指向適當的virtual table(s)。

            ü          如果有一個member沒有出現在member initialization list中,但它有default constructor,調用之。

            ü          將member initialization list中的data members的初始化操作以members的聲明次序放進constructor的函數本身。

            2002-8-18

            5.3對象復制語意學 Object Copy Semantics
            1.   只有在默認行為所導致的語意不安全或者不正確以致發生別名化aliasing或者內存泄漏memory leak時,才需要設計一個copy assignment operator。否則,程序反倒會執行得較慢。

            2.   如果僅僅是為了把NRV優化開關打開而提供一個copy constructor,那么就沒有必要一定要提供一個copy assignment operator。

            3.   copy assignment operator有一個非正交情況,那就是它缺乏一個平行于member initialization list的member assignment list。調用base class的copy assignment operator示例:

            Point::operator = (p3d); 或 (*(Point*)this) = p3d; 或 (Point &)(*this) = p3d;

            4.   事實上,copy assignment operator在虛擬繼承情況下行為不佳,需要小心設計和說明。許多編譯器甚至并不嘗試取得正確的語意,它們在每一個中間的copy assignment operator中調用每一個base class instance,于是造成virtual base copy assignment operator的多個實體被調用。建議盡可能不要允許一個virtual base class的拷貝操作,并不要在任何virtual base class中聲明data member。

            5.5解構語意學 Semantics of Destruction
            如果class沒有定義destructor,那么只有在其內帶的member object或base class擁有destructor時,編譯器才會自動合成出一個destructor。一個由程序員定義的destructor被擴展的方式類似constructors被擴展的方式,只是順序相反:

            ü          destructor的函數本體首先被執行;

            ü          如果class擁有member class objects,而后者擁有destructors,那么它們將以聲明的相反順序而調用;

            ü          如果object內帶一個vptr,則現在被重新設定以指向適當base class之virtual table;

            ü          如果有任何直接的nonvirtual base classes擁有destructor,它們將以聲明的相反順序而調用;

            ü          如果有任何virtual base classes擁有destructor,而前面討論的這個class是most-derived class,那么它們會以原先構造順序的相反順序被調用。

            2002-8-19

            執行期語意學 Runtime Semantics
            6.1對象的構造和解構
            1.   一般而言,constructor和destructor的安插都如你所預期。但如果一個區段或函數中有一個以上的離開點,情況就會復雜一些,destructor會放在每一個離開點之前。通常,我們要求將object盡可能放在使用它的那個程序區附近,這樣做可以節省不必要的對象產生和銷毀操作。

            2.   C++程序中所有的global objects都被放置在程序的data segment中,如果不明確指定初值,object所配置的內存內容將為0(C并不自動設定初值)。如果global object有constructor和destructor的話,我們說它需要靜態的初始化和內存釋放操作。

            2002-8-20

            3.   virtual base class的subobject在每個derived class中的位置可能會變動,不能在編譯時期確定。以一個derived class的pointer或reference來存取virtual base class subobject,是一種nonconstant expression,必須在執行期方可評估求值。

            4.   使用靜態初始化的object有一些缺點。其一,無法放入try區段,任何throw操作必將觸發exception handling library的默認函數terminate();其二,程序員必須為控制“需要跨越模塊做靜態初始化”objects的依賴順序而產生的復雜度付出代價。建議根本就不要使用那些需要靜態初始化的global objects。

            5.   新的C++標準要求編譯單位中的static local class objects必須在相應函數第一次被調用時才被構造,而且必須以相反的次序銷毀。由于這些objects是在需要時才被構造,因此編譯時期無法預期其集合和順序。為支持新標準,可能要對被產生出來的static local class objects保持一個執行期鏈表。

            2003-8-1

            6.   對于對象數組定義,晚近的編譯器一般會提供兩個函數,分別用于處理沒有virtual base class的class,以及內帶virtual base class的class ,它們通常被稱為vec_new、vec_vnew。前者類型通常為:

            void* vec_new(                                                   // 初始化程序員未提供初值的連續元素

                       void *array,                                               // 數組起始地址若為0,則動態分配

                       size_t elem_size,                                       // 每一個class object的大小

                       int elem_count,                                         // 數組中的元素數目

                       void (*constructor) (void *),                    // class的default constructor指針

                       void (*destructor) (void *, char)               // class的destructor指針,以0填入

            ); 如果程序員提供帶有默認參數值的default constructor,編譯器要做特殊處理,以傳入默認參數值!

            對應銷毀數組的兩個函數分別為vec_delete、vec_vdelete。前者類型通常為:

            void* vec_delete(

                       void *array,                                               // 數組起始地址

                       size_t elem_size,                                       // 每一個class object的大小

                       int elem_count,                                         // 數組中的元素數目

                       void (*destructor) (void *, char)               // class的destructor指針

            );

            6.2 new和delete運算符
                     注意區分operator new和new operator!前者負責分配內存;后者先調用前者分配內存,然后調用constructor以實施初始化。

            《深度探索C++對象模型》讀書筆記

             

            本文來自CSDN博客,轉載請標明出處:http://blog.csdn.net/xjtuse_mal/archive/2007/03/01/1517809.aspx

            posted @ 2010-10-14 10:36 Vincent 閱讀(419) | 評論 (0)編輯 收藏

            僅列出標題  下一頁
            久久精品国产72国产精福利| 久久亚洲高清观看| 久久久久久亚洲精品无码| 青青草原1769久久免费播放| 成人国内精品久久久久一区| 99久久婷婷国产综合亚洲| 久久精品嫩草影院| 久久精品国产精品亚洲艾草网美妙 | 老司机午夜网站国内精品久久久久久久久| A狠狠久久蜜臀婷色中文网| 国产亚洲婷婷香蕉久久精品| 久久精品无码一区二区三区| 久久精品国产欧美日韩| 欧美日韩精品久久久久| 综合网日日天干夜夜久久| 久久久久久夜精品精品免费啦| 国产成人久久精品一区二区三区| 精品国产福利久久久| 久久精品成人一区二区三区| 久久久噜噜噜久久中文字幕色伊伊| 国产69精品久久久久9999APGF| 久久综合综合久久综合| 一本久久a久久精品综合夜夜| 香蕉aa三级久久毛片 | 影音先锋女人AV鲁色资源网久久 | 国产精品九九九久久九九| 国产精品免费看久久久香蕉| 亚洲色欲久久久久综合网| 久久99国产乱子伦精品免费| 久久国产成人午夜AV影院| 久久人人妻人人爽人人爽| 狠狠色综合久久久久尤物 | 97久久精品无码一区二区天美| 91精品国产综合久久香蕉| 久久精品国产99久久久古代| 国产精品美女久久久网AV| 午夜天堂精品久久久久| 人人狠狠综合久久亚洲高清| 久久久久久免费一区二区三区| 伊人久久大香线蕉av不变影院| 国内精品久久久久久久coent |