• <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>
            xiaoguozi's Blog
            Pay it forword - 我并不覺的自豪,我所嘗試的事情都失敗了······習慣原本生活的人不容易改變,就算現狀很糟,他們也很難改變,在過程中,他們還是放棄了······他們一放棄,大家就都是輸家······讓愛傳出去,很困難,也無法預料,人們需要更細心的觀察別人,要隨時注意才能保護別人,因為他們未必知道自己要什么·····
            經典語錄1:

            哈佛有一個著名的理論:人的差別在于業余時間,而一個人的命運決定于晚上8點到10點之間。每晚抽出2個小時的時間用來閱讀、進修、思考或參加有意的演講、討論,你會發現,你的人生正在發生改變,堅持數年之后,成功會向你招手。不要每天抱著QQ/MSN/游戲/電影/肥皂劇……奮斗到12點都舍不得休息,看就看一些勵志的影視或者文章,不要當作消遣;學會思考人生,學會感悟人生,慢慢的,你的人生將會發生變化……

            經典語錄2: 

            無論你的收入是多少,記得分成五份進行規劃投資:增加對身體的投資,讓身體始終好用;增加對社交的投資,擴大你的人脈;增加對學習的投資,加強你的自信;增加對旅游的投資,擴大你的見聞;增加對未來的投資,增加你的收益。好好規劃落實,你會發現你的人生逐步會有大量盈余。

            經典語錄3: 

            過去的一頁,能不翻就不要翻,翻落了灰塵會迷了雙眼。有些人說不出哪里好,但就是誰都替代不了! 那些以前說著永不分離的人,早已經散落在天涯了。收拾起心情,繼續走吧,錯過花,你將收獲雨,錯過這一個,你才會遇到下一個。

            經典語錄4: 

            被人誤解的時候能微微的一笑,這是一種素養;受委屈的時候能坦然的一笑,這是一種大度;吃虧的時候能開心的一笑,這是一種豁達;無奈的時候能達觀的一笑,這是一種境界;危難的時候能泰然一笑,這是一種大氣;被輕蔑的時候能平靜的一笑,這是一種自信;失戀的時候能輕輕的一笑,這是一種灑脫。

            經典語錄5: 

            人生途中,有些是無法逃避的,比如命運;有些是無法更改的,比如情緣;有些是難以磨滅的,比如記憶;有些是難以擱置的,比如愛戀……與其被動地承受,不如勇敢地面對;與其鳥宿檐下,不如擊翅風雨;與其在沉默中孤寂,不如在抗爭中爆發……路越艱,阻越大,險越多,只要走過去了,人生就會更精彩。

            經典語錄6: 

            你改變不了環境,但你可以改變自己;你改變不了事實,但你可以改變態度;你改變不了過去,但你可以改變現在;你不能控制他人,但你可以掌握自己;你不能預知明天,但你可以把握今天;你不可以樣樣順利,但你可以事事盡心;你不能延伸生命的長度,但你可以決定生命的寬度。

            經典語錄7: 

            魅力女人:1、善于發現生活里的美。2、養成看書的習慣。3、擁有品位。4、跟有思想的人交朋友。5、遠離泡沫偶像劇。6、學會忍耐與寬容。7、培養健康的心態,重視自己的身體。8、離開任何一個男人,都會活得很好。9、有著理財的動機,學習投資經營。10、尊重感情,珍惜緣分。

            經典語錄8: 

            愚人向遠方尋找快樂,智者則在自己身旁培養快樂。生活里的每一個細節都蘊藏著快樂,只是在于你是否感受到了而已。快樂著的人,每一件事,每一個人身上,他都能發現能令自己歡悅的因素來,并讓快樂擴張,鼓舞和影響了周圍的人。

            經典語錄9: 

            【給自己安慰的10句溫馨話】1、最重要的是今天的心;2、別總是自己跟自己過不去;3、用心做自己該做的事;4、不要過于計較別人評價;5、每個人都有自己的活法;6、喜歡自己才會擁抱生活;7、不必一味討好別人;8、木已成舟便要順其自然;9、不妨暫時丟開煩心事;10、自己感覺幸福就是幸福。

            經典語錄10: 

            沒有永遠的緣份,沒有永遠的生命,我們所能擁有的,可能只是平凡的一生。然而因為有你,生命便全然不同,不用誓言,不必承諾,我們只需依了愛緣,以目光為媒,印證三生石上的約定,便牽了手,不必緊握,卻永不放松,以自己設計的愛的程式,去演繹一種精典的永恒。

            經典語錄11: 

            我們之所以會心累,就是常常徘徊在堅持和放棄之間,舉棋不定。我們之所以會煩惱,就是記性太好,該記的,不該記的都會留在記憶里。我們之所以會痛苦,就是追求的太多。我們之所以不快樂,就是計較的太多,不是我們擁有的太少,而是我們計較的太多。

            經典語錄12: 

            男人吸引女人的10個特質:1.真實 2.深刻 3.胸懷 4.敢為 5.風度 6.機靈 7.幽默 8.進取 9.浪漫 10.冒險.女人吸引男人的10個特點:1.溫柔 2.知性 3.直性 4.涵養 5.朦朧 6.小動作 7.勤于家事 8.膚白 9.性感著裝 10.香氛

            經典語錄13: 

            真正的愛,是接受,不是忍受;是支持,不是支配;是慰問,不是質問;真正的愛,要道謝也要道歉。要體貼,也要體諒。要認錯,也好改錯;真正的愛,不是彼此凝視,而是共同沿著同一方向望去。其實,愛不是尋找一個完美的人。而是,要學會用完美的眼光,欣賞一個并不完美的人。

            經典語錄14: 

            身邊總有些人,你看見他整天都開心,率真得像個小孩,人人都羨慕他;其實,你哪里知道:前一秒人后還傷心地流著淚的他,后一秒人前即刻洋溢燦爛笑容。他們其實沒有能力獨處,夜深人靜時,總坐在窗前對著夜空冥想失意的苦楚。他們就像向日葵,向著太陽的正面永遠明媚鮮亮,在照不到的背面卻將悲傷深藏。

            經典語錄15: 

            生命中,有些人來了又去,有些人去而復返,有些人近在咫尺,有些人遠在天涯,有些人擦身而過,有些人一路同行。或許在某兩條路的盡頭相遇,結伴同行了一段路程,又在下一個分岔路口道別。無論如何,終免不了曲終人散的傷感。遠在天涯的朋友:或許已是遙遠得無法問候,但還是謝謝您曾經的結伴同行。

            經典語錄16: 

            愛情很簡單,因為每個人都會說:“我愛你,會為你付出一切!”,愛情很難,因為沒有多少人做到了他的承諾。如果真心愛一個人,不承諾也會去愛;如果不愛一個人,曾經承諾也會背叛。

            經典語錄17: 

            【你最后悔什么】某雜志對全國60歲以上的老人抽樣調查:第一名:75%的人后悔年輕時努力不夠,導致一事無成。第二名:70%的人后悔在年輕的時候選錯了職業。第三名:62%的人后悔對子女教育不當。第四名:57%的人后悔沒有好好珍惜自己的伴侶。第五名:49%的人后悔沒有善待自己的身體。

            經典語錄18: 

            【做人十心機】⒈做人不能太單純 適度偽裝自己 ⒉凡事留余地 要留退路 ⒊話不說絕口無遮攔難成大事 ⒋成熟而不世故 ⒌心態好 想得開活得不累 ⒍懂方圓之道:沒事不惹事,來事不怕事 ⒎不可少二禮:禮儀與禮物 ⒏人在江湖飄 防挨朋友刀 ⒐偶爾"勢利眼" 尋可靠伙伴 ⒑放下面子來做人。

            經典語錄19: 

            人生旅途中,總有人不斷地走來,有人不斷地離去。當新的名字變成老的名字,當老的名字漸漸模糊,又是一個故事的結束和另一個故事的開始。在不斷的相遇和錯開中,終于明白:身邊的人只能陪著自己走過或近或遠的一程,而不能伴自己一生;陪伴一生的是自己的名字和那些或清晰或模糊的名字所帶來的感動。

            經典語錄20: 

            從現在開始,聰明一點,不要問別人想不想你,愛不愛你?若是要想你或者愛你自然會對你說,但是從你的嘴里說出來,別人會很驕傲和不在乎你。再也不要太在意一些人,太在乎一些事,順其自然以最佳心態面對,因為這個世界就是這樣:往往在最在乎的事物面前,我們最沒有價值。

            經典語錄21: 

            一個人的成就,不是以金錢衡量,而是一生中,你善待過多少人,有多少人懷念你。生意人的賬簿,記錄收入與支出,兩數相減,便是盈利。人生的賬簿,記錄愛與被愛,兩數相加,就是成就。
            posted @ 2011-05-09 17:57 小果子 閱讀(183) | 評論 (0)編輯 收藏

            引言

            本書主要針對的是 C++ 程序的性能優化,深入介紹 C++ 程序性能優化的方法和實例。全書由 4 個篇組成,第 1 篇介紹 C++ 語言的對象模型,該篇是優化 C++ 程序的基礎;第 2 篇主要針對如何優化 C++ 程序的內存使用;第 3 篇介紹如何優化程序的啟動性能;第 4 篇介紹了三類性能優化工具,即內存分析工具、性能分析工具和 I/O 檢測工具,它們是測量程序性能的利器。

            本章首先簡單介紹自定義內存池性能優化的原理,然后列舉軟件開發中常用的內存池的不同類型,并給出具體實現的實例。

            6.1 自定義內存池性能優化的原理

             

            圖書信息 書名:《C++應用程序性能優化》
            作者:馮宏華、徐瑩、程遠、汪磊 等編著
            出版社:電子工業出版社
            出版日期:2007 年 03 月
            ISBN:978-7-121-03831-0
            購買: 中國互動出版網dearbook

            推薦章節:

             

            更多推薦書籍,請訪問 developerWorks 圖書頻道

            歡迎您對本書提出寶貴的反饋意見。您可以通過本頁面最下方的 建議 欄目為本文打分,并反饋您的建議和意見。

            如果您對 developerWorks 圖書頻道有什么好的建議,歡迎您將建議發給我們

             

            如前所述,讀者已經了解到"堆"和"棧"的區別。而在編程實踐中,不可避免地要大量用到堆上的內存。例如在程序中維護一個鏈表的數據結構時,每次新增或者刪除一個鏈表的節點,都需要從內存堆上分配或者釋放一定的內存;在維護一個動態數組時,如果動態數組的大小不能滿足程序需要時,也要在內存堆上分配新的內存空間。

            6.1.1 默認內存管理函數的不足

            利用默認的內存管理函數new/delete或malloc/free在堆上分配和釋放內存會有一些額外的開銷。

            系統在接收到分配一定大小內存的請求時,首先查找內部維護的內存空閑塊表,并且需要根據一定的算法(例如分配最先找到的不小于申請大小的內存塊給請求者,或者分配最適于申請大小的內存塊,或者分配最大空閑的內存塊等)找到合適大小的空閑內存塊。如果該空閑內存塊過大,還需要切割成已分配的部分和較小的空閑塊。然后系統更新內存空閑塊表,完成一次內存分配。類似地,在釋放內存時,系統把釋放的內存塊重新加入到空閑內存塊表中。如果有可能的話,可以把相鄰的空閑塊合并成較大的空閑塊。

            默認的內存管理函數還考慮到多線程的應用,需要在每次分配和釋放內存時加鎖,同樣增加了開銷。

            可見,如果應用程序頻繁地在堆上分配和釋放內存,則會導致性能的損失。并且會使系統中出現大量的內存碎片,降低內存的利用率。

            默認的分配和釋放內存算法自然也考慮了性能,然而這些內存管理算法的通用版本為了應付更復雜、更廣泛的情況,需要做更多的額外工作。而對于某一個具體的應用程序來說,適合自身特定的內存分配釋放模式的自定義內存池則可以獲得更好的性能。

            6.1.2 內存池的定義和分類

            自定義內存池的思想通過這個"池"字表露無疑,應用程序可以通過系統的內存分配調用預先一次性申請適當大小的內存作為一個內存池,之后應用程序自己對內存的分配和釋放則可以通過這個內存池來完成。只有當內存池大小需要動態擴展時,才需要再調用系統的內存分配函數,其他時間對內存的一切操作都在應用程序的掌控之中。

            應用程序自定義的內存池根據不同的適用場景又有不同的類型。

            從線程安全的角度來分,內存池可以分為單線程內存池和多線程內存池。單線程內存池整個生命周期只被一個線程使用,因而不需要考慮互斥訪問的問題;多線程內存池有可能被多個線程共享,因此則需要在每次分配和釋放內存時加鎖。相對而言,單線程內存池性能更高,而多線程內存池適用范圍更廣。

            從內存池可分配內存單元大小來分,可以分為固定內存池和可變內存池。所謂固定內存池是指應用程序每次從內存池中分配出來的內存單元大小事先已經確定,是固定不變的;而可變內存池則每次分配的內存單元大小可以按需變化,應用范圍更廣,而性能比固定內存池要低。

            6.1.3 內存池工作原理示例

            下面以固定內存池為例說明內存池的工作原理,如圖6-1所示。


            圖6-1 固定內存池
            圖6-1  固定內存池

            固定內存池由一系列固定大小的內存塊組成,每一個內存塊又包含了固定數量和大小的內存單元。

            如圖6-1所示,該內存池一共包含4個內存塊。在內存池初次生成時,只向系統申請了一個內存塊,返回的指針作為整個內存池的頭指針。之后隨著應用程序對內存的不斷需求,內存池判斷需要動態擴大時,才再次向系統申請新的內存塊,并把所有這些內存塊通過指針鏈接起來。對于操作系統來說,它已經為該應用程序分配了4個等大小的內存塊。由于是大小固定的,所以分配的速度比較快;而對于應用程序來說,其內存池開辟了一定大小,內存池內部卻還有剩余的空間。

            例如放大來看第4個內存塊,其中包含一部分內存池塊頭信息和3個大小相等的內存池單元。單元1和單元3是空閑的,單元2已經分配。當應用程序需要通過該內存池分配一個單元大小的內存時,只需要簡單遍歷所有的內存池塊頭信息,快速定位到還有空閑單元的那個內存池塊。然后根據該塊的塊頭信息直接定位到第1個空閑的單元地址,把這個地址返回,并且標記下一個空閑單元即可;當應用程序釋放某一個內存池單元時,直接在對應的內存池塊頭信息中標記該內存單元為空閑單元即可。

            可見與系統管理內存相比,內存池的操作非常迅速,它在性能優化方面的優點主要如下。

            (1)針對特殊情況,例如需要頻繁分配釋放固定大小的內存對象時,不需要復雜的分配算法和多線程保護。也不需要維護內存空閑表的額外開銷,從而獲得較高的性能。

            (2)由于開辟一定數量的連續內存空間作為內存池塊,因而一定程度上提高了程序局部性,提升了程序性能。

            (3)比較容易控制頁邊界對齊和內存字節對齊,沒有內存碎片的問題。

            6.2 一個內存池的實現實例

            本節分析在某個大型應用程序實際應用到的一個內存池實現,并詳細講解其使用方法與工作原理。這是一個應用于單線程環境且分配單元大小固定的內存池,一般用來為執行時會動態頻繁地創建且可能會被多次創建的類對象或者結構體分配內存。

            本節首先講解該內存池的數據結構聲明及圖示,接著描述其原理及行為特征。然后逐一講解實現細節,最后介紹如何在實際程序中應用此內存池,并與使用普通內存函數申請內存的程序性能作比較。

            6.2.1 內部構造

            內存池類MemoryPool的聲明如下:


            class MemoryPool
                        {
                        private:
                        MemoryBlock*   pBlock;
                        USHORT          nUnitSize;
                        USHORT          nInitSize;
                        USHORT          nGrowSize;
                        public:
                        MemoryPool( USHORT nUnitSize,
                        USHORT nInitSize = 1024,
                        USHORT nGrowSize = 256 );
                        ~MemoryPool();
                        void*           Alloc();
                        void            Free( void* p );
                        };
                        

            MemoryBlock為內存池中附著在真正用來為內存請求分配內存的內存塊頭部的結構體,它描述了與之聯系的內存塊的使用信息:


            struct MemoryBlock
                        {
                        USHORT          nSize;
                        USHORT          nFree;
                        USHORT          nFirst;
                        USHORT          nDummyAlign1;
                        MemoryBlock*  pNext;
                        char            aData[1];
                        static void* operator new(size_t, USHORT nTypes, USHORT nUnitSize)
                        {
                        return ::operator new(sizeof(MemoryBlock) + nTypes * nUnitSize);
                        }
                        static void  operator delete(void *p, size_t)
                        {
                        ::operator delete (p);
                        }
                        MemoryBlock (USHORT nTypes = 1, USHORT nUnitSize = 0);
                        ~MemoryBlock() {}
                        };
                        

            此內存池的數據結構如圖6-2所示。


            圖6-2 內存池的數據結構
            圖6-2  內存池的數據結構

            6.2.2 總體機制

            此內存池的總體機制如下。

            (1)在運行過程中,MemoryPool內存池可能會有多個用來滿足內存申請請求的內存塊,這些內存塊是從進程堆中開辟的一個較大的連續內存區域,它由一個MemoryBlock結構體和多個可供分配的內存單元組成,所有內存塊組成了一個內存塊鏈表,MemoryPool 的pBlock是這個鏈表的頭。對每個內存塊,都可以通過其頭部的MemoryBlock結構體的pNext成員訪問緊跟在其后面的那個內存塊。

            (2)每個內存塊由兩部分組成,即一個MemoryBlock結構體和多個內存分配單元。這些內存分配單元大小固定(由 MemoryPool的nUnitSize表示),MemoryBlock結構體并不維護那些已經分配的單元的信息;相反,它只維護沒有分配的自由分配單元的信息。它有兩個成員比較重要:nFree和nFirst。nFree記錄這個內存塊中還有多少個自由分配單元,而nFirst則記錄下一個可供分配的單元的編號。每一個自由分配單元的頭兩個字節(即一個USHORT型值)記錄了緊跟它之后的下一個自由分配單元的編號,這樣,通過利用每個自由分配單元的頭兩個字節,一個MemoryBlock中的所有自由分配單元被鏈接起來。

            (3)當有新的內存請求到來時,MemoryPool會通過pBlock遍歷MemoryBlock鏈表,直到找到某個 MemoryBlock所在的內存塊,其中還有自由分配單元(通過檢測MemoryBlock結構體的nFree成員是否大于0)。如果找到這樣的內存塊,取得其MemoryBlock的nFirst值(此為該內存塊中第1個可供分配的自由單元的編號)。然后根據這個編號定位到該自由分配單元的起始位置(因為所有分配單元大小固定,因此每個分配單元的起始位置都可以通過編號分配單元大小來偏移定位),這個位置就是用來滿足此次內存申請請求的內存的起始地址。但在返回這個地址前,需要首先將該位置開始的頭兩個字節的值(這兩個字節值記錄其之后的下一個自由分配單元的編號)賦給本內存塊的 MemoryBlock的nFirst成員。這樣下一次的請求就會用這個編號對應的內存單元來滿足,同時將此內存塊的MemoryBlock的nFree 遞減1,然后才將剛才定位到的內存單元的起始位置作為此次內存請求的返回地址返回給調用者。

            (4)如果從現有的內存塊中找不到一個自由的內存分配單元(當第1次請求內存,以及現有的所有內存塊中的所有內存分配單元都已經被分配時會發生這種情形),MemoryPool就會從進程堆中申請一個內存塊(這個內存塊包括一個MemoryBlock結構體,及緊鄰其后的多個內存分配單元,假設內存分配單元的個數為n,n可以取值MemoryPool中的nInitSize或者nGrowSize),申請完后,并不會立刻將其中的一個分配單元分配出去,而是需要首先初始化這個內存塊。初始化的操作包括設置MemoryBlock的nSize為所有內存分配單元的大小(注意,并不包括MemoryBlock結構體的大小)、nFree為n-1(注意,這里是n-1而不是n,因為此次新內存塊就是為了滿足一次新的內存請求而申請的,馬上就會分配一塊自由存儲單元出去,如果設為n-1,分配一個自由存儲單元后無須再將n遞減1),nFirst為1(已經知道nFirst為下一個可以分配的自由存儲單元的編號。為1的原因與nFree為n-1相同,即立即會將編號為0的自由分配單元分配出去。現在設為1,其后不用修改nFirst的值),MemoryBlock的構造需要做更重要的事情,即將編號為0的分配單元之后的所有自由分配單元鏈接起來。如前所述,每個自由分配單元的頭兩個字節用來存儲下一個自由分配單元的編號。另外,因為每個分配單元大小固定,所以可以通過其編號和單元大小(MemoryPool的nUnitSize成員)的乘積作為偏移值進行定位。現在唯一的問題是定位從哪個地址開始?答案是MemoryBlock的aData[1]成員開始。因為aData[1]實際上是屬于MemoryBlock結構體的(MemoryBlock結構體的最后一個字節),所以實質上,MemoryBlock結構體的最后一個字節也用做被分配出去的分配單元的一部分。因為整個內存塊由MemoryBlock結構體和整數個分配單元組成,這意味著內存塊的最后一個字節會被浪費,這個字節在圖6-2中用位于兩個內存的最后部分的濃黑背景的小塊標識。確定了分配單元的起始位置后,將自由分配單元鏈接起來的工作就很容易了。即從aData位置開始,每隔nUnitSize大小取其頭兩個字節,記錄其之后的自由分配單元的編號。因為剛開始所有分配單元都是自由的,所以這個編號就是自身編號加1,即位置上緊跟其后的單元的編號。初始化后,將此內存塊的第1個分配單元的起始地址返回,已經知道這個地址就是aData。

            (5)當某個被分配的單元因為delete需要回收時,該單元并不會返回給進程堆,而是返回給MemoryPool。返回時,MemoryPool能夠知道該單元的起始地址。這時,MemoryPool開始遍歷其所維護的內存塊鏈表,判斷該單元的起始地址是否落在某個內存塊的地址范圍內。如果不在所有內存地址范圍內,則這個被回收的單元不屬于這個MemoryPool;如果在某個內存塊的地址范圍內,那么它會將這個剛剛回收的分配單元加到這個內存塊的MemoryBlock所維護的自由分配單元鏈表的頭部,同時將其nFree值遞增1。回收后,考慮到資源的有效利用及后續操作的性能,內存池的操作會繼續判斷:如果此內存塊的所有分配單元都是自由的,那么這個內存塊就會從MemoryPool中被移出并作為一個整體返回給進程堆;如果該內存塊中還有非自由分配單元,這時不能將此內存塊返回給進程堆。但是因為剛剛有一個分配單元返回給了這個內存塊,即這個內存塊有自由分配單元可供下次分配,因此它會被移到MemoryPool維護的內存塊的頭部。這樣下次的內存請求到來,MemoryPool遍歷其內存塊鏈表以尋找自由分配單元時,第1次尋找就會找到這個內存塊。因為這個內存塊確實有自由分配單元,這樣可以減少MemoryPool的遍歷次數。

            綜上所述,每個內存池(MemoryPool)維護一個內存塊鏈表(單鏈表),每個內存塊由一個維護該內存塊信息的塊頭結構(MemoryBlock)和多個分配單元組成,塊頭結構MemoryBlock則進一步維護一個該內存塊的所有自由分配單元組成的"鏈表"。這個鏈表不是通過"指向下一個自由分配單元的指針"鏈接起來的,而是通過"下一個自由分配單元的編號"鏈接起來,這個編號值存儲在該自由分配單元的頭兩個字節中。另外,第1個自由分配單元的起始位置并不是MemoryBlock結構體"后面的"第1個地址位置,而是MemoryBlock結構體"內部"的最后一個字節aData(也可能不是最后一個,因為考慮到字節對齊的問題),即分配單元實際上往前面錯了一位。又因為MemoryBlock結構體后面的空間剛好是分配單元的整數倍,這樣依次錯位下去,內存塊的最后一個字節實際沒有被利用。這么做的一個原因也是考慮到不同平臺的移植問題,因為不同平臺的對齊方式可能不盡相同。即當申請MemoryBlock大小內存時,可能會返回比其所有成員大小總和還要大一些的內存。最后的幾個字節是為了"補齊",而使得 aData成為第1個分配單元的起始位置,這樣在對齊方式不同的各種平臺上都可以工作。

            6.2.3 細節剖析

            有了上述的總體印象后,本節來仔細剖析其實現細節。

            (1)MemoryPool的構造如下:


            MemoryPool::MemoryPool( USHORT _nUnitSize,
                        USHORT _nInitSize, USHORT _nGrowSize )
                        {
                        pBlock      = NULL;	            ①
                        nInitSize   = _nInitSize;       ②
                        nGrowSize   = _nGrowSize;       ③
                        if ( _nUnitSize > 4 )
                        nUnitSize = (_nUnitSize + (MEMPOOL_ALIGNMENT-1)) & ~(MEMPOOL_ALIGNMENT-1); ④
                        else if ( _nUnitSize <= 2 )
                        nUnitSize = 2;              ⑤
                        else
                        nUnitSize = 4;
                        }
                        

            從①處可以看出,MemoryPool創建時,并沒有立刻創建真正用來滿足內存申請的內存塊,即內存塊鏈表剛開始時為空。

            ②處和③處分別設置"第1次創建的內存塊所包含的分配單元的個數",及"隨后創建的內存塊所包含的分配單元的個數",這兩個值在MemoryPool創建時通過參數指定,其后在該MemoryPool對象生命周期中一直不變。

            后面的代碼用來設置nUnitSize,這個值參考傳入的_nUnitSize參數。但是還需要考慮兩個因素。如前所述,每個分配單元在自由狀態時,其頭兩個字節用來存放"其下一個自由分配單元的編號"。即每個分配單元"最少"有"兩個字節",這就是⑤處賦值的原因。④處是將大于4個字節的大小_nUnitSize往上"取整到"大于_nUnitSize的最小的MEMPOOL_ ALIGNMENT的倍數(前提是MEMPOOL_ALIGNMENT為2的倍數)。如_nUnitSize為11 時,MEMPOOL_ALIGNMENT為8,nUnitSize為16;MEMPOOL_ALIGNMENT為4,nUnitSize為 12;MEMPOOL_ALIGNMENT為2,nUnitSize為12,依次類推。

            (2)當向MemoryPool提出內存請求時:


            void* MemoryPool::Alloc()
                        {
                        if ( !pBlock )           ①
                        {
                        ……
                        }
                        MemoryBlock* pMyBlock = pBlock;
                        while (pMyBlock && !pMyBlock->nFree )②
                        pMyBlock = pMyBlock->pNext;
                        if ( pMyBlock )	         ③
                        {
                        char* pFree = pMyBlock->aData+(pMyBlock->nFirst*nUnitSize);
                        pMyBlock->nFirst = *((USHORT*)pFree);
                        pMyBlock->nFree--;
                        return (void*)pFree;
                        }
                        else                    ④
                        {
                        if ( !nGrowSize )
                        return NULL;
                        pMyBlock = new(nGrowSize, nUnitSize) FixedMemBlock(nGrowSize, nUnitSize);
                        if ( !pMyBlock )
                        return NULL;
                        pMyBlock->pNext = pBlock;
                        pBlock = pMyBlock;
                        return (void*)(pMyBlock->aData);
                        }
                        }
                        

            MemoryPool滿足內存請求的步驟主要由四步組成。

            ①處首先判斷內存池當前內存塊鏈表是否為空,如果為空,則意味著這是第1次內存申請請求。這時,從進程堆中申請一個分配單元個數為nInitSize的內存塊,并初始化該內存塊(主要初始化MemoryBlock結構體成員,以及創建初始的自由分配單元鏈表,下面會詳細分析其代碼)。如果該內存塊申請成功,并初始化完畢,返回第1個分配單元給調用函數。第1個分配單元以MemoryBlock結構體內的最后一個字節為起始地址。

            ②處的作用是當內存池中已有內存塊(即內存塊鏈表不為空)時遍歷該內存塊鏈表,尋找還有"自由分配單元"的內存塊。

            ③處檢查如果找到還有自由分配單元的內存塊,則"定位"到該內存塊現在可以用的自由分配單元處。"定位"以 MemoryBlock結構體內的最后一個字節位置aData為起始位置,以MemoryPool的nUnitSize為步長來進行。找到后,需要修改 MemoryBlock的nFree信息(剩下來的自由分配單元比原來減少了一個),以及修改此內存塊的自由存儲單元鏈表的信息。在找到的內存塊中,pMyBlock->nFirst為該內存塊中自由存儲單元鏈表的表頭,其下一個自由存儲單元的編號存放在 pMyBlock->nFirst指示的自由存儲單元(亦即剛才定位到的自由存儲單元)的頭兩個字節。通過剛才定位到的位置,取其頭兩個字節的值,賦給pMyBlock->nFirst,這就是此內存塊的自由存儲單元鏈表的新的表頭,即下一次分配出去的自由分配單元的編號(如果nFree大于零的話)。修改維護信息后,就可以將剛才定位到的自由分配單元的地址返回給此次申請的調用函數。注意,因為這個分配單元已經被分配,而內存塊無須維護已分配的分配單元,因此該分配單元的頭兩個字節的信息已經沒有用處。換個角度看,這個自由分配單元返回給調用函數后,調用函數如何處置這塊內存,內存池無從知曉,也無須知曉。此分配單元在返回給調用函數時,其內容對于調用函數來說是無意義的。因此幾乎可以肯定調用函數在用這個單元的內存時會覆蓋其原來的內容,即頭兩個字節的內容也會被抹去。因此每個存儲單元并沒有因為需要鏈接而引入多余的維護信息,而是直接利用單元內的頭兩個字節,當其分配后,頭兩個字節也可以被調用函數利用。而在自由狀態時,則用來存放維護信息,即下一個自由分配單元的編號,這是一個有效利用內存的好例子。

            ④處表示在②處遍歷時,沒有找到還有自由分配單元的內存塊,這時,需要重新向進程堆申請一個內存塊。因為不是第一次申請內存塊,所以申請的內存塊包含的分配單元個數為nGrowSize,而不再是nInitSize。與①處相同,先做這個新申請內存塊的初始化工作,然后將此內存塊插入MemoryPool的內存塊鏈表的頭部,再將此內存塊的第1個分配單元返回給調用函數。將此新內存塊插入內存塊鏈表的頭部的原因是該內存塊還有很多可供分配的自由分配單元(除非nGrowSize等于1,這應該不太可能。因為內存池的含義就是一次性地從進程堆中申請一大塊內存,以供后續的多次申請),放在頭部可以使得在下次收到內存申請時,減少②處對內存塊的遍歷時間。

            可以用圖6-2的MemoryPool來展示MemoryPool::Alloc的過程。圖6-3是某個時刻MemoryPool的內部狀態。


            圖6-3 某個時刻MemoryPool的內部狀態
            圖6-3  某個時刻MemoryPool的內部狀態

            因為MemoryPool的內存塊鏈表不為空,因此會遍歷其內存塊鏈表。又因為第1個內存塊里有自由的分配單元,所以會從第1個內存塊中分配。檢查nFirst,其值為m,這時pBlock->aData+(pBlock->nFirst*nUnitSize) 定位到編號為m的自由分配單元的起始位置(用pFree表示)。在返回pFree之前,需要修改此內存塊的維護信息。首先將nFree遞減1,然后取得 pFree處開始的頭兩個字節的值(需要說明的是,這里aData處值為k。其實不是這一個字節。而是以aData和緊跟其后的另外一個字節合在一起構成的一個USHORT的值,不可誤會)。發現為k,這時修改pBlock的nFirst為k。然后,返回pFree。此時MemoryPool的結構如圖 6-4所示。


            圖6-4 MemoryPool的結構
            圖6-4  MemoryPool的結構

            可以看到,原來的第1個可供分配的單元(m編號處)已經顯示為被分配的狀態。而pBlock的nFirst已經指向原來m單元下一個自由分配單元的編號,即k。

            (3)MemoryPool回收內存時:


            void MemoryPool::Free( void* pFree )
                        {
                        ……
                        MemoryBlock* pMyBlock = pBlock;
                        while ( ((ULONG)pMyBlock->aData > (ULONG)pFree) ||
                        ((ULONG)pFree >= ((ULONG)pMyBlock->aData + pMyBlock->nSize)) )①
                        {
                        ……
                        }
                        pMyBlock->nFree++;                     ②
                        *((USHORT*)pFree) = pMyBlock->nFirst;  ③
                        pMyBlock->nFirst = (USHORT)(((ULONG)pFree-(ULONG)(pBlock->aData)) / nUnitSize);④
                        if (pMyBlock->nFree*nUnitSize == pMyBlock->nSize )⑤
                        {
                        ……
                        }
                        else
                        {
                        ……
                        }
                        }
                        

            如前所述,回收分配單元時,可能會將整個內存塊返回給進程堆,也可能將被回收分配單元所屬的內存塊移至內存池的內存塊鏈表的頭部。這兩個操作都需要修改鏈表結構。這時需要知道該內存塊在鏈表中前一個位置的內存塊。

            ①處遍歷內存池的內存塊鏈表,確定該待回收分配單元(pFree)落在哪一個內存塊的指針范圍內,通過比較指針值來確定。

            運行到②處,pMyBlock即找到的包含pFree所指向的待回收分配單元的內存塊(當然,這時應該還需要檢查 pMyBlock為NULL時的情形,即pFree不屬于此內存池的范圍,因此不能返回給此內存池,讀者可以自行加上)。這時將pMyBlock的 nFree遞增1,表示此內存塊的自由分配單元多了一個。

            ③處用來修改該內存塊的自由分配單元鏈表的信息,它將這個待回收分配單元的頭兩個字節的值指向該內存塊原來的第一個可分配的自由分配單元的編號。

            ④處將pMyBlock的nFirst值改變為指向這個待回收分配單元的編號,其編號通過計算此單元的起始位置相對pMyBlock的aData位置的差值,然后除以步長(nUnitSize)得到。

            實質上,③和④兩步的作用就是將此待回收分配單元"真正回收"。值得注意的是,這兩步實際上是使得此回收單元成為此內存塊的下一個可分配的自由分配單元,即將它放在了自由分配單元鏈表的頭部。注意,其內存地址并沒有發生改變。實際上,一個分配單元的內存地址無論是在分配后,還是處于自由狀態時,一直都不會變化。變化的只是其狀態(已分配/自由),以及當其處于自由狀態時在自由分配單元鏈表中的位置。

            ⑤處檢查當回收完畢后,包含此回收單元的內存塊的所有單元是否都處于自由狀態,且此內存是否處于內存塊鏈表的頭部。如果是,將此內存塊整個的返回給進程堆,同時修改內存塊鏈表結構。

            注意,這里在判斷一個內存塊的所有單元是否都處于自由狀態時,并沒有遍歷其所有單元,而是判斷nFree乘以 nUnitSize是否等于nSize。nSize是內存塊中所有分配單元的大小,而不包括頭部MemoryBlock結構體的大小。這里可以看到其用意,即用來快速檢查某個內存塊中所有分配單元是否全部處于自由狀態。因為只需結合nFree和nUnitSize來計算得出結論,而無須遍歷和計算所有自由狀態的分配單元的個數。

            另外還需注意的是,這里并不能比較nFree與nInitSize或nGrowSize的大小來判斷某個內存塊中所有分配單元都為自由狀態,這是因為第1次分配的內存塊(分配單元個數為nInitSize)可能被移到鏈表的后面,甚至可能在移到鏈表后面后,因為某個時間其所有單元都處于自由狀態而被整個返回給進程堆。即在回收分配單元時,無法判定某個內存塊中的分配單元個數到底是nInitSize還是nGrowSize,也就無法通過比較nFree與nInitSize或nGrowSize的大小來判斷一個內存塊的所有分配單元是否都為自由狀態。

            以上面分配后的內存池狀態作為例子,假設這時第2個內存塊中的最后一個單元需要回收(已被分配,假設其編號為m,pFree指針指向它),如圖6-5所示。

            不難發現,這時nFirst的值由原來的0變為m。即此內存塊下一個被分配的單元是m編號的單元,而不是0編號的單元(最先分配的是最新回收的單元,從這一點看,這個過程與棧的原理類似,即先進后出。只不過這里的"進"意味著"回收",而"出"則意味著"分配")。相應地,m的"下一個自由單元"標記為0,即內存塊原來的"下一個將被分配出去的單元",這也表明最近回收的分配單元被插到了內存塊的"自由分配單元鏈表"的頭部。當然,nFree遞增1。


            圖6-5 分配后的內存池狀態
            圖6-5  分配后的內存池狀態

            處理至⑥處之前,其狀態如圖6-6所示。


            圖6-6 處理至⑥處之前的內存池狀態
            圖6-6  處理至⑥處之前的內存池狀態

            這里需要注意的是,雖然pFree被"回收",但是pFree仍然指向m編號的單元,這個單元在回收過程中,其頭兩個字節被覆寫,但其他部分的內容并沒有改變。而且從整個進程的內存使用角度來看,這個m編號的單元的狀態仍然是"有效的"。因為這里的"回收"只是回收給了內存池,而并沒有回收給進程堆,因此程序仍然可以通過pFree訪問此單元。但是這是一個很危險的操作,因為首先該單元在回收過程中頭兩個字節已被覆寫,并且該單元可能很快就會被內存池重新分配。因此回收后通過pFree指針對這個單元的訪問都是錯誤的,讀操作會讀到錯誤的數據,寫操作則可能會破壞程序中其他地方的數據,因此需要格外小心。

            接著,需要判斷該內存塊的內部使用情況,及其在內存塊鏈表中的位置。如果該內存塊中省略號"……"所表示的其他部分中還有被分配的單元,即nFree乘以nUnitSize不等于nSize。因為此內存塊不在鏈表頭,因此還需要將其移到鏈表頭部,如圖6-7所示。


            圖6-7 因回收引起的MemoryBlock移動
            圖6-7  因回收引起的MemoryBlock移動

            如果該內存塊中省略號"……"表示的其他部分中全部都是自由分配單元,即nFree乘以nUnitSize等于nSize。因為此內存塊不在鏈表頭,所以此時需要將此內存塊整個回收給進程堆,回收后內存池的結構如圖6-8所示。


            圖6-8 回收后內存池的結構
            圖6-8  回收后內存池的結構

            一個內存塊在申請后會初始化,主要是為了建立最初的自由分配單元鏈表,下面是其詳細代碼:


            MemoryBlock::MemoryBlock (USHORT nTypes, USHORT nUnitSize)
                        : nSize  (nTypes * nUnitSize),
                        nFree  (nTypes - 1),                     ④
                        nFirst (1),                              ⑤
                        pNext  (0)
                        {
                        char * pData = aData;                  ①
                        for (USHORT i = 1; i < nTypes; i++) ②
                        {
                        *reinterpret_cast<USHORT*>(pData) = i; ③
                        pData += nUnitSize;
                        }
                        }
                        

            這里可以看到,①處pData的初值是aData,即0編號單元。但是②處的循環中i卻是從1開始,然后在循環內部的③處將pData的頭兩個字節值置為i。即0號單元的頭兩個字節值為1,1號單元的頭兩個字節值為2,一直到(nTypes-2)號單元的頭兩個字節值為(nTypes-1)。這意味著內存塊初始時,其自由分配單元鏈表是從0號開始。依次串聯,一直到倒數第2個單元指向最后一個單元。

            還需要注意的是,在其初始化列表中,nFree初始化為nTypes-1(而不是nTypes),nFirst初始化為 1(而不是0)。這是因為第1個單元,即0編號單元構造完畢后,立刻會被分配。另外注意到最后一個單元初始并沒有設置頭兩個字節的值,因為該單元初始在本內存塊中并沒有下一個自由分配單元。但是從上面例子中可以看到,當最后一個單元被分配并回收后,其頭兩個字節會被設置。

            圖6-9所示為一個內存塊初始化后的狀態。


            圖6-9 一個內存塊初始化后的狀態
            圖6-9  一個內存塊初始化后的狀態

            當內存池析構時,需要將內存池的所有內存塊返回給進程堆:


            MemoryPool::~MemoryPool()
                        {
                        MemoryBlock* pMyBlock = pBlock;
                        while ( pMyBlock )
                        {
                        ……
                        }
                        }
                        

            6.2.4 使用方法

            分析內存池的內部原理后,本節說明如何使用它。從上面的分析可以看到,該內存池主要有兩個對外接口函數,即Alloc和 Free。Alloc返回所申請的分配單元(固定大小內存),Free則回收傳入的指針代表的分配單元的內存給內存池。分配的信息則通過 MemoryPool的構造函數指定,包括分配單元大小、內存池第1次申請的內存塊中所含分配單元的個數,以及內存池后續申請的內存塊所含分配單元的個數等。

            綜上所述,當需要提高某些關鍵類對象的申請/回收效率時,可以考慮將該類所有生成對象所需的空間都從某個這樣的內存池中開辟。在銷毀對象時,只需要返回給該內存池。"一個類的所有對象都分配在同一個內存池對象中"這一需求很自然的設計方法就是為這樣的類聲明一個靜態內存池對象,同時為了讓其所有對象都從這個內存池中開辟內存,而不是缺省的從進程堆中獲得,需要為該類重載一個new運算符。因為相應地,回收也是面向內存池,而不是進程的缺省堆,還需要重載一個delete運算符。在new運算符中用內存池的Alloc函數滿足所有該類對象的內存請求,而銷毀某對象則可以通過在 delete運算符中調用內存池的Free完成。

            6.2.5 性能比較

            為了測試利用內存池后的效果,通過一個很小的測試程序可以發現采用內存池機制后耗時為297 ms。而沒有采用內存池機制則耗時625 ms,速度提高了52.48%。速度提高的原因可以歸結為幾點,其一,除了偶爾的內存申請和銷毀會導致從進程堆中分配和銷毀內存塊外,絕大多數的內存申請和銷毀都由內存池在已經申請到的內存塊中進行,而沒有直接與進程堆打交道,而直接與進程堆打交道是很耗時的操作;其二,這是單線程環境的內存池,可以看到內存池的Alloc和Free操作中并沒有加線程保護措施。因此如果類A用到該內存池,則所有類A對象的創建和銷毀都必須發生在同一個線程中。但如果類A 用到內存池,類B也用到內存池,那么類A的使用線程可以不必與類B的使用線程是同一個線程。

            另外,在第1章中已經討論過,因為內存池技術使得同類型的對象分布在相鄰的內存區域,而程序會經常對同一類型的對象進行遍歷操作。因此在程序運行過程中發生的缺頁應該會相應少一些,但這個一般只能在真實的復雜應用環境中進行驗證。

            6.3 本章小結

            內存的申請和釋放對一個應用程序的整體性能影響極大,甚至在很多時候成為某個應用程序的瓶頸。消除內存申請和釋放引起的瓶頸的方法往往是針對內存使用的實際情況提供一個合適的內存池。內存池之所以能夠提高性能,主要是因為它能夠利用應用程序的實際內存使用場景中的某些"特性"。比如某些內存申請與釋放肯定發生在一個線程中,某種類型的對象生成和銷毀與應用程序中的其他類型對象要頻繁得多,等等。針對這些特性,可以為這些特殊的內存使用場景提供量身定做的內存池。這樣能夠消除系統提供的缺省內存機制中,對于該實際應用場景中的不必要的操作,從而提升應用程序的整體性能。

            posted @ 2011-04-05 20:18 小果子 閱讀(2060) | 評論 (0)編輯 收藏

             內存泄露相信對C++程序員來說都不陌生。解決內存泄露的方案多種多樣,大部分方案以追蹤檢測為主,這種方法實現起來容易,使用方便,也比較安全。

                     首先我們要確定這個模塊的主要功能:

            1. 能追蹤內存的分配和釋放過程。
            2. 要能顯示內存分配的相關信息,比如內存塊大小,代碼所在文件所在行等。
            3. 在發現內存泄露時及時給出相關信息。
            4. 能正確處理一些異常情況,比如內存不足,對象初始化失敗等等。
            5. 是線程安全的。[*這個還沒有實現]

                    有了一些基本功能需求,我們需要考慮每種功能怎么去實現。首先,我們可以通過重載的方式來追蹤new,delete.malloc和free,C++給我提供了這樣的特性。因為本文主要針對C++,所以主要講重載new,delete的方法,malloc和free的重載實現于此類似,最終版本的程序中也實現了malloc和free的重載。

            1.重載new和delete

                    首先我們要了解一下new和delete是怎么工作的。C++中的操作符最終都會被轉換成函數形式,例如"new int"會變成"opetaor new(sizeof(int))",而"new double[10]"會變成"operator new(sizeof(double)*10)",同樣“delete p”就變成了"operator delete(p)"。另外一個需要特別注意的地方是,new對于用戶定義的數據類型(即你的自定義類)會自動調用該類型的構造函數,如果構造函數沒有拋出異常,則正確分配,否則會中斷分配操作,將異常傳遞給用戶。默認情況下,new可以對象構造異常進行捕獲。另外一個版本的new就是不帶捕獲異常功能的的了,所以C++系統提供的new和delete有:

            1
            2
            3
            4
            5
            6
            7
            8
            9
            void* operator new(size_t size)throw(std::bad_alloc);
            void* operator new[](size_t size) throw(std::bad_alloc);
            void* operator new(size_t,std::nothrow_t&)throw();
            void* operator new[](size_t,std::nothrow_t&)throw();
             
            void  operator delete(void* pointer);
            void  operator delete[](void* pointer);
            void  operator delete(void* pointer,std::nothrow_t&);
            void  operator delete[](void* pointer,std::nothrow_t&);<br>

                    其中,nothrow_t是一個空結構體“struct nothrow_t{}",它的一個實例就是nothrow,C++用它來區分可以捕獲異常的new和不可捕獲異常的new。我們不能直接修改內部函數的行為,但是我們可以重載它們。為了實現提供內存分配信息的功能,我們給重載的函數加上幾個參數。得到以下函數:

            1
            2
            3
            4
            5
            6
            7
            8
            9
            10
            11
            12
            13
            14
            void* operator new(size_t size);
            void* operator new[](size_t size);
            void* operator new(size_t,std::nothrow_t&)throw();
            void* operator new[](size_t,std::nothrow_t&)throw();
            void* operator new(size_t size,const char* file,const char* func,const int line)throw(std::bad_alloc);
            void* operator new[](size_t size,const char* file,const char* func,const int line) throw(std::bad_alloc);
            void* operator delete(void* pointer);
            void* operator delete[](void* pointer);
            /*******Placement Delete********/
            void  operator delete(void* pointer,const char* file,const char* func,const int line);
            void  operator delete[](void* pointer,const char* file,const char* func,const int line);
            void  operator delete(void* pointer,std::nothrow_t&);
            void  operator delete[](void* pointer,std::nothrow_t&);
            /*******************************/

                     中間的幾個函數,就是我們主要需要重載的函數,模塊的大部分工作也都由著幾個函數完成。這些函數參數中,file表示分配代碼所在的文件名,func表示代碼所在的函數名,line表示代碼行號。這幾個參數信息我們可以通過編譯器預定義好的幾個宏來獲得:__FILE__,__FUNCTION__,__LINE__。也就是說可以將"new ..."替換成"new(__FILE__,__FUNCTION__,__LINE__) ...",最終成為"operator new(sizeof(...),__FILE__,__FUNCTION__,__LINE__)"的形式,也就達到了我們的目的。關于 placement delete將在下面詳細解釋。

            2.空間分配

                    接下來我們要考慮內存分配信息的組織問題了。我們先來了解一下編譯器是怎么組織的。在大部分編譯器中,new所分配的空間都要大于實際申請的空間,大出來的部分就是編譯器定義的內存塊的信息,包括了內存塊的大小還有一些其他信息。如下圖所示:

                    我們把包含內存分配信息的部分叫做cookie數據。為了方便,我們把cookie數據放在分配的內存的起始位置,之后緊接有效數據區。我們還需要把返回給調用者的指針和new分配的數據區聯系起來,原本想用性能比較好的STL的map數據結構來儲存這些數據,但是map內部同樣也使用new來分配內存,所以如果直接使用map來儲存,就會陷入死循環中。所以這里我們必須自己現實一個數據結構。我們可以對返回給調用者的地址進行Hash,得到hash 表中的地址,具有相同Hash值的數據我們用一個單向鏈表連接起來。最終的數據結構如下圖所示:

            2.1.構造函數中的異常

                    另外一個必須要注意的一點是,new操作符會先分配空間然后調用用戶自定義類型的構造函數,如果構造函數拋出異常,需要用戶手動釋放已分配的內存。問題在于釋放這樣的內存不能用一般的delete操作符,可以用一個例子來說明:

            1
            2
            3
            4
            5
            6
            7
            8
            9
            10
            11
            12
            13
            14
            15
            16
            17
            18
            19
            20
            21
            22
            23
            24
            25
            26
            #include <stdio.h>
            #include <stdexcept>
             
            void* operator new(size_t size, int line) {
                printf("Allocate %u bytes on line %d\\n", size, line);
                return operator new(size);
            }
             
            class UserClass {
            public:
                UserClass(int n)throw(){
                    if(n<=0){
                         throw std::runtime_error("n must be positive");
                    }
                }
            };
             
            int main(){
                try{
                    UserClass* myobj=new(__LINE__) UserClass(-10);
                    delete myobj; //doesn't work if placement was not defined
                } catch(const std::runtime_error& e) {
                    fprintf(stderr,"Exception: %s\n",e.what());
                }
                return 0;
            }<br>

                    這里,雖然在new過后試圖使用delete釋放已經分配的內存,但是實際上不會釋放。也許你的編譯器會給出這樣一條消息:

                    “no matching operator delete found”

                    為了正確處理這種情況,并給用戶提供相關的信息,我們需要定義placement delete操作符。placement delete是C++98標準中才有的一個特性,所以對于某些老的編譯器(大致可以認為是指那些98年以前編寫的編譯器)不支持這個特性。這需要在模塊中添加宏定義讓用戶可以關閉placement delete的定義,以便模塊能在較老的編譯器上編譯。以下就是需要定義的placement delete操作符:

            1
            2
            3
            4
            void  operator delete(void* pointer,const char* file,const char* func,const int line);
            void  operator delete[](void* pointer,const char* file,const char* func,const int line);
            void  operator delete(void* pointer,std::nothrow_t&);
            void  operator delete[](void* pointer,std::nothrow_t&);<br>

            3.檢查內存泄露

                    有了上面的實現,我們可以方便的手動檢測內存泄露。通過一個函數來實現,它會檢索整個hash表,如果表不為空則存在內存泄露。

                    為了達到在最后程序退出時檢查內存泄露的目的,我們需要在所有對象調用析構函數后進行內存泄露檢測,這是因為某些用戶類型在構造函數里調用new而在析構函數里調用delete。這樣做能大大的減小誤報的概率。而且因為對象的析構函數的調用往往在主函數main()執行結束之后進行,所以我們也不能直接在主函數里進行內存泄露檢測。這里我們利用一個全局對象來實現這種檢測。首先我們定義一個類:

            1
            2
            3
            4
            5
            6
            7
            8
            9
            10
            11
            class MemCheck{
                public:
                    MemCheck(){
                        memset(pTable,0,sizeof(mc_block_node_t*) * MC_HASHTABLESIZE);
                    }
                    ~MemCheck(){
                        if(mc_checkmem()){
                            abort();
                        }
                    }
            };

                    這里的構造函數初始化Hash表。析構函數檢測內存泄露。然后定義一個MemCheck的全局靜態對象,這樣當程序運行之前會初始化hash表,程序退出時檢測內存泄露。可是問題又來了,如果一個程序中有多個全局靜態對象會怎樣?不幸的是,對于全局靜態對象的構造順序和析構順序是C++標準中的一個未定義問題,也就是說,這個順序取決于編譯器的具體實現。考慮,絕大多數平臺使用VC和GCC編譯器,我們可以針對這兩種編譯器來控制全局對象的構造和解析順序。

            1
            2
            3
            4
            5
            6
            7
            8
            9
            #ifdef _MSC_VER
            #pragma init_seg(lib)
            #endif
             
            static MemCheck mc_autocheck_object
            #ifdef __GNUC__
            __attribute__((init_priority (101)))
            #endif
            ;

                    這里的宏定義部分都是編譯器的選項。

            posted @ 2011-04-05 20:11 小果子 閱讀(1626) | 評論 (1)編輯 收藏
            提交服務器處理業務后結果返回頁面的處理,Struts2提供了對不同種類返回結果的支持,常見的有JSP,FreeMarker,Velocity等。

            struts.xml配置文件中result的語法:<result name="" type="">xxxxx</result>

            Struts2支持的不同類型的返回結果為:

            Chain Result-->type="chain"
            用來處理Action鏈

            Dispatcher Result -->type="dispatcher"
            用來轉向頁面,通常處理JSP

            FreeMarker Result -->type="freemarker"
            處理FreeMarker模板

            HttpHeader Result -->type="httpheader"
            用來控制特殊的Http行為

            Redirect Result -->type="redirect"
            重定向到一個URL

            Redirect Action Result -->type="redirectAction"
            重定向到一個Action

            Stream Result -->type="stream"
            向瀏覽器發送InputSream對象,通常用來處理文件下載

            Velocity Result -->type="velocity"
            處理Velocity模板

            XLST Result -->type="xslt"
            處理XML/XLST模板

            PlainText Result -->type="plainText"
            顯示原始文件內容,例如文件源代碼


            另外第三方的result類型還包括JasperReports Plugin,專門用來處理JasperReport類型的報表輸出。

            在struts-default.xml文件中已經有了對于所有類型Result的定義:

            Java 代碼

            1. <result-types>  
            2.  
            3.     <result-type name="chain"  
            4.  
            5.              class="com.opensymphony.xwork2.ActionChainResult"/>  
            6.  
            7.     <result-type name="dispatcher"  
            8.  
            9.              class="org.apache.struts2.dispatcher.ServletDispatcherResult"  
            10.  
            11.              default="true"/>  
            12.  
            13.     <result-type name="freemarker"  
            14.  
            15.              class="org.apache.struts2.views.freemarker.FreemarkerResult"/>  
            16.  
            17.     <result-type name="httpheader"  
            18.  
            19.              class="org.apache.struts2.dispatcher.HttpHeaderResult"/>  
            20.  
            21.     <result-type name="redirect"  
            22.  
            23.              class="org.apache.struts2.dispatcher.ServletRedirectResult"/>  
            24.  
            25.     <result-type name="redirectAction"  
            26.  
            27.              class="org.apache.struts2.dispatcher.ServletActionRedirectResult"/>  
            28.  
            29.     <result-type name="stream"  
            30.  
            31.              class="org.apache.struts2.dispatcher.StreamResult"/>  
            32.  
            33.     <result-type name="velocity"  
            34.  
            35.              class="org.apache.struts2.dispatcher.VelocityResult"/>  
            36.  
            37.     <result-type name="xslt"  
            38.  
            39.              class="org.apache.struts2.views.xslt.XSLTResult"/>  
            40.  
            41.     <result-type name="plainText"  
            42.  
            43.              class="org.apache.struts2.dispatcher.PlainTextResult" />  
            44.  
            45.     <!-- Deprecated name form scheduled for removal in Struts 2.1.0.  
            46.  
            47.          The camelCase versions are preferred. See ww-1707 -->  
            48.  
            49.     <result-type name="redirect-action"  
            50.  
            51.              class="org.apache.struts2.dispatcher.ServletActionRedirectResult"/>  
            52.  
            53.     <result-type name="plaintext"  
            54.  
            55.              class="org.apache.struts2.dispatcher.PlainTextResult" />  
            56.  
            57. </result-types>  

            <result-types>

            <result-type name="chain"

            class="com.opensymphony.xwork2.ActionChainResult"/>

            <result-type name="dispatcher"

            class="org.apache.struts2.dispatcher.ServletDispatcherResult"

            default="true"/>

            <result-type name="freemarker"

            class="org.apache.struts2.views.freemarker.FreemarkerResult"/>

            <result-type name="httpheader"

            class="org.apache.struts2.dispatcher.HttpHeaderResult"/>

            <result-type name="redirect"

            class="org.apache.struts2.dispatcher.ServletRedirectResult"/>

            <result-type name="redirectAction"

            class="org.apache.struts2.dispatcher.ServletActionRedirectResult"/>

            <result-type name="stream"

            class="org.apache.struts2.dispatcher.StreamResult"/>

            <result-type name="velocity"

            class="org.apache.struts2.dispatcher.VelocityResult"/>

            <result-type name="xslt"

            class="org.apache.struts2.views.xslt.XSLTResult"/>

            <result-type name="plainText"

            class="org.apache.struts2.dispatcher.PlainTextResult" />

            <!-- Deprecated name form scheduled for removal in Struts 2.1.0.

            The camelCase versions are preferred. See ww-1707 -->

            <result-type name="redirect-action"

            class="org.apache.struts2.dispatcher.ServletActionRedirectResult"/>

            <result-type name="plaintext"

            class="org.apache.struts2.dispatcher.PlainTextResult" />

            </result-types>



            從上述代碼中可以看出在不指定Result類型的時候默認使用dispatcher類型。


            定義一個Result值,

            Java 代碼

            1. <result name="success" type="dispatcher">  
            2.  
            3.     <param name="location">/myjsp.jsp</param>  
            4.  
            5. </result>  

            <result name="success" type="dispatcher">

            <param name="location">/myjsp.jsp</param>

            </result>


            由于type默認值是dispatcher,所以這里不需要定義,另外name的默認值為success所以這里也不需要定義。
            上述代碼可以簡寫為:

            Java 代碼

            1. <result>  
            2.  
            3.     <param name="location">/myjsp.jsp</param>  
            4.  
            5. </result>  

            <result>

            <param name="location">/myjsp.jsp</param>

            </result>




            另外location參數也可以直接卸載result標簽內部(也就是無需再result里面使用),所以上述代碼的最簡單的寫法為:

            Java 代碼

            1. <result>/myjsp.jsp</result>  

            <result>/myjsp.jsp</result>



            我們也可以定義多個不同的result

            Java 代碼

            1. <action name="Hello">  
            2.  
            3. <result>/hello/hello.jsp</result>  
            4.  
            5. <result name="error">/hello/error.jsp</result>  
            6.  
            7. <result name="input">/hello/input.jsp</result>  
            8.  
            9. </action>  

            <action name="Hello">

            <result>/hello/hello.jsp</result>

            <result name="error">/hello/error.jsp</result>

            <result name="input">/hello/input.jsp</result>

            </action>



            上 述代碼的含義為,名字為Hello的Action有三個返回結果,并且都是 dispatcher類型(默認類型),這三個返回值的名字分別為success(默認值),error,input(當輸入不通過時,action 方法返回input),對應的頁面的路徑分別為 /hello/result.jsp,/hello/error.jsp,/hello/input.jsp。

            有些時候我們需要一個定義在全局的result,這個時候我們可以在package內部定義全局的result,例如:

            Java 代碼

            1. <global-results>  
            2.  
            3. <result name="error">/error.jsp</result>  
            4.  
            5. <result name="invalid.token">/error.jsp</result>  
            6.  
            7. <result name="login" type="redirect-action">login!input</result>  
            8.  
            9. </global-results>  

            <global-results>

            <result name="error">/error.jsp</result>

            <result name="invalid.token">/error.jsp</result>

            <result name="login" type="redirect-action">login!input</result>

            </global-results>



            動態返回結果

            有些時候,只有當Action執行完璧的時候我們才知道要返回哪個結果,這個時候我們可以在Action內部定義一個屬性,這個屬性用來存儲 Action執行完璧之后的Result值,例如:

            Java 代碼

            1. private String nextAction;  
            2.  
            3. public String getNextAction() {  
            4.  
            5.     return nextAction;  
            6.  
            7. }  

            private String nextAction;

            public String getNextAction() {

            return nextAction;

            }



            在strutx.xml配置文件中,我們可以使用${nextAction}來引用到Action中的屬性,通過${nextAction}表示的內容來動態的返回結果,例如:

            Java 代碼

            1. <action name="fragment" class="FragmentAction">  
            2.  
            3. <result name="next" type="redirect-action">${nextAction}</result>  
            4.  
            5. </action>  

            <action name="fragment" class="FragmentAction">

            <result name="next" type="redirect-action">${nextAction}</result>

            </action>



            上述Action的execute方法返回next的時候,還需要根據nextAction的屬性來判斷具體定位到哪個Action。


            在struts.xml配置文件中,我們可以使用method=""來設置調用類的哪個方法,這樣就可以在一個JAVA類中使用不同的方法來實現不同的功能,就無需每個功能寫一類了,例如:
            Java 代碼

            1. <action name="fragment" class="cn.com.web.FragmentAction" method="add">  
            2.       <result>/success.jsp</result>  
            3. </action> 
            posted @ 2011-04-05 18:17 小果子 閱讀(528) | 評論 (0)編輯 收藏

            在struts的配置文件中的形式為:

            <constant name="struts.i18n.encoding" value="UTF-8" />

            struts.action.extension
            The URL extension to use to determine if the request is meant for a Struts action
            用URL擴展名來確定是否這個請求是被用作Struts action,其實也就是設置 action的后綴,例如login.do的'do'字。

            struts.configuration
            The org.apache.struts2.config.Configuration implementation class
            org.apache.struts2.config.Configuration接口名

            struts.configuration.files
            A list of configuration files automatically loaded by Struts
            struts自動加載的一個配置文件列表

            struts.configuration.xml.reload
            Whether to reload the XML configuration or not
            是否加載xml配置(true,false)

            struts.continuations.package
            The package containing actions that use Rife continuations
            含有actions的完整連續的package名稱

            struts.custom.i18n.resources
            Location of additional localization properties files to load
            加載附加的國際化屬性文件(不包含.properties后綴)

            struts.custom.properties
            Location of additional configuration properties files to load
            加載附加的配置文件的位置


            struts.devMode
            Whether Struts is in development mode or not
            是否為struts開發模式

            struts.dispatcher.parametersWorkaround
            Whether to use a Servlet request parameter workaround necessary for some versions of WebLogic
            (某些版本的weblogic專用)是否使用一個servlet請求參數工作區(PARAMETERSWORKAROUND)

            struts.enable.DynamicMethodInvocation
            Allows one to disable dynamic method invocation from the URL
            允許動態方法調用

            struts.freemarker.manager.classname
            The org.apache.struts2.views.freemarker.FreemarkerManager implementation class
            org.apache.struts2.views.freemarker.FreemarkerManager接口名

            struts.i18n.encoding
            The encoding to use for localization messages
            國際化信息內碼

            struts.i18n.reload
            Whether the localization messages should automatically be reloaded
            是否國際化信息自動加載

            struts.locale
            The default locale for the Struts application
            默認的國際化地區信息

            struts.mapper.class
            The org.apache.struts2.dispatcher.mapper.ActionMapper implementation class
            org.apache.struts2.dispatcher.mapper.ActionMapper接口

            struts.multipart.maxSize
            The maximize size of a multipart request (file upload)
            multipart請求信息的最大尺寸(文件上傳用)

            struts.multipart.parser
            The org.apache.struts2.dispatcher.multipart.MultiPartRequest parser implementation for a multipart request (file upload)
            專為multipart請求信息使用的 org.apache.struts2.dispatcher.multipart.MultiPartRequest解析器接口(文件上傳用)


            struts.multipart.saveDir
            The directory to use for storing uploaded files
            設置存儲上傳文件的目錄夾

            struts.objectFactory
            The com.opensymphony.xwork2.ObjectFactory implementation class
            com.opensymphony.xwork2.ObjectFactory接口(spring)

            struts.objectFactory.spring.autoWire
            Whether Spring should autoWire or not
            是否自動綁定Spring

            struts.objectFactory.spring.useClassCache
            Whether Spring should use its class cache or not
            是否spring應該使用自身的cache

            struts.objectTypeDeterminer
            The com.opensymphony.xwork2.util.ObjectTypeDeterminer implementation class
            com.opensymphony.xwork2.util.ObjectTypeDeterminer接口

            struts.serve.static.browserCache
            If static content served by the Struts filter should set browser caching header properties or not
            是否struts過濾器中提供的靜態內容應該被瀏覽器緩存在頭部屬性中

            struts.serve.static
            Whether the Struts filter should serve static content or not
            是否struts過濾器應該提供靜態內容

            struts.tag.altSyntax
            Whether to use the alterative syntax for the tags or not
            是否可以用替代的語法替代tags

            struts.ui.templateDir
            The directory containing UI templates
            UI templates的目錄夾

            struts.ui.theme
            The default UI template theme
            默認的UI template主題

            struts.url.http.port
            The HTTP port used by Struts URLs
            設置http端口

            struts.url.https.port
            The HTTPS port used by Struts URLs
            設置https端口

            struts.url.includeParams
            The default includeParams method to generate Struts URLs
            在url中產生 默認的includeParams


            struts.velocity.configfile
            The Velocity configuration file path
            velocity配置文件路徑

            struts.velocity.contexts
            List of Velocity context names
            velocity的context列表


            struts.velocity.manager.classname
            org.apache.struts2.views.velocity.VelocityManager implementation class
            org.apache.struts2.views.velocity.VelocityManager接口名

            struts.velocity.toolboxlocation
            The location of the Velocity toolbox
            velocity工具盒的位置
            struts.xslt.nocache
            Whether or not XSLT templates should not be cached
            是否XSLT模版應該被緩存

            【原創】struts2的struts.properties配置文件詳解

            struts.action.extension
            The URL extension to use to determine if the request is meant for a Struts action
            用URL擴展名來確定是否這個請求是被用作Struts action,其實也就是設置 action的后綴,例如login.do的'do'字。

            struts.configuration
            The org.apache.struts2.config.Configuration implementation class
            org.apache.struts2.config.Configuration接口名

            struts.configuration.files
            A list of configuration files automatically loaded by Struts
            struts自動加載的一個配置文件列表

            struts.configuration.xml.reload
            Whether to reload the XML configuration or not
            是否加載xml配置(true,false)

            struts.continuations.package
            The package containing actions that use Rife continuations
            含有actions的完整連續的package名稱

            struts.custom.i18n.resources
            Location of additional localization properties files to load
            加載附加的國際化屬性文件(不包含.properties后綴)

            struts.custom.properties
            Location of additional configuration properties files to load
            加載附加的配置文件的位置


            struts.devMode
            Whether Struts is in development mode or not
            是否為struts開發模式

            struts.dispatcher.parametersWorkaround
            Whether to use a Servlet request parameter workaround necessary for some versions of WebLogic
            (某些版本的weblogic專用)是否使用一個servlet請求參數工作區(PARAMETERSWORKAROUND)

            struts.enable.DynamicMethodInvocation
            Allows one to disable dynamic method invocation from the URL
            允許動態方法調用

            struts.freemarker.manager.classname
            The org.apache.struts2.views.freemarker.FreemarkerManager implementation class
            org.apache.struts2.views.freemarker.FreemarkerManager接口名

            struts.i18n.encoding
            The encoding to use for localization messages
            國際化信息內碼

            struts.i18n.reload
            Whether the localization messages should automatically be reloaded
            是否國際化信息自動加載

            struts.locale
            The default locale for the Struts application
            默認的國際化地區信息

            struts.mapper.class
            The org.apache.struts2.dispatcher.mapper.ActionMapper implementation class
            org.apache.struts2.dispatcher.mapper.ActionMapper接口

            struts.multipart.maxSize
            The maximize size of a multipart request (file upload)
            multipart請求信息的最大尺寸(文件上傳用)

            struts.multipart.parser
            The org.apache.struts2.dispatcher.multipart.MultiPartRequest parser implementation for a multipart request (file upload)
            專為multipart請求信息使用的 org.apache.struts2.dispatcher.multipart.MultiPartRequest解析器接口(文件上傳用)


            struts.multipart.saveDir
            The directory to use for storing uploaded files
            設置存儲上傳文件的目錄夾

            struts.objectFactory
            The com.opensymphony.xwork2.ObjectFactory implementation class
            com.opensymphony.xwork2.ObjectFactory接口(spring)

            struts.objectFactory.spring.autoWire
            Whether Spring should autoWire or not
            是否自動綁定Spring

            struts.objectFactory.spring.useClassCache
            Whether Spring should use its class cache or not
            是否spring應該使用自身的cache

            struts.objectTypeDeterminer
            The com.opensymphony.xwork2.util.ObjectTypeDeterminer implementation class
            com.opensymphony.xwork2.util.ObjectTypeDeterminer接口

            struts.serve.static.browserCache
            If static content served by the Struts filter should set browser caching header properties or not
            是否struts過濾器中提供的靜態內容應該被瀏覽器緩存在頭部屬性中

            struts.serve.static
            Whether the Struts filter should serve static content or not
            是否struts過濾器應該提供靜態內容

            struts.tag.altSyntax
            Whether to use the alterative syntax for the tags or not
            是否可以用替代的語法替代tags

            struts.ui.templateDir
            The directory containing UI templates
            UI templates的目錄夾

            struts.ui.theme
            The default UI template theme
            默認的UI template主題

            struts.url.http.port
            The HTTP port used by Struts URLs
            設置http端口

            struts.url.https.port
            The HTTPS port used by Struts URLs
            設置https端口

            struts.url.includeParams
            The default includeParams method to generate Struts URLs
            在url中產生 默認的includeParams


            struts.velocity.configfile
            The Velocity configuration file path
            velocity配置文件路徑

            struts.velocity.contexts
            List of Velocity context names
            velocity的context列表


            struts.velocity.manager.classname
            org.apache.struts2.views.velocity.VelocityManager implementation class
            org.apache.struts2.views.velocity.VelocityManager接口名

            struts.velocity.toolboxlocation
            The location of the Velocity toolbox
            velocity工具盒的位置
            struts.xslt.nocache
            Whether or not XSLT templates should not be cached
            是否XSLT模版應該被緩存

            struts2加載常量的順序
            struts-default.xml
            struts-plugin.xml
            struts.xml
            struts.properties
            web.xml
            后面的會覆蓋掉前面的常量,最好在struts.xml中定義

             

            怎么由.action改為.do
            <constant name="struts.action.extension" value="do"/>
            do或action
            <constant name="struts.action.extension" value="do,action"/>


            truts2用來指定默認編碼的
            <constant name="struts.i18n.encoding" value="UTF-8"/>

             

            改變常量后不許重啟服務器
            <constant name="struts.configuration.xml.reload" value="true"/>
            系統默認為false 


            便于排錯,打印出更詳細的錯誤信息
            <constant name="struts.devMode" value="true">

             

            設置瀏覽器是否緩存靜態內容,默認為TRUE  開發階段最好關閉
            <constant name="struts.server.static.browserCache" valur="false"/>

             

            默認的視圖主題
            <constant name="struts.ui.theme" value="simple"/>

             

            與spring集成時,指定spring負責action對象的創建
            <struts name="struts.objectFactory" value="spring"/>

             

            上傳文件大小限制
            <struts name="struts.multipart.maxSize" value="10241024"/>

            posted @ 2011-04-05 17:58 小果子 閱讀(477) | 評論 (0)編輯 收藏
            僅列出標題
            共58頁: First 24 25 26 27 28 29 30 31 32 Last 
            久久久久久综合网天天| 91精品国产91久久综合| 精品久久久无码人妻中文字幕 | 狠狠色综合网站久久久久久久| 精品久久久久久久久久中文字幕 | 久久精品国产99国产精品导航 | 精品久久久久久国产三级 | 久久久久无码国产精品不卡| 久久无码中文字幕东京热| 2020久久精品国产免费| 久久综合色区| 精品久久久无码人妻中文字幕豆芽| 狠狠综合久久综合中文88| 亚洲va中文字幕无码久久不卡| 麻豆精品久久精品色综合| 久久精品国产男包| 久久精品国产亚洲一区二区三区| 亚洲AV日韩精品久久久久久| 国产精品日韩深夜福利久久| 久久国产乱子伦免费精品| 久久午夜无码鲁丝片秋霞 | 国产精品免费福利久久| 日日狠狠久久偷偷色综合免费 | 国色天香久久久久久久小说| 国产成人无码精品久久久免费| 色狠狠久久AV五月综合| 亚洲人成无码www久久久| 99久久精品这里只有精品| 久久精品国产亚洲av日韩| 国产精品中文久久久久久久| 精品久久久久中文字幕一区| 久久香蕉综合色一综合色88| 久久精品草草草| 国产V亚洲V天堂无码久久久| 午夜久久久久久禁播电影| 久久精品国产99国产精品导航 | 亚洲精品无码久久毛片| 国产2021久久精品| 一级做a爰片久久毛片人呢| 国产精品久久久久…| 久久99国产精品99久久|