• <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>
            asm, c, c++ are my all
            -- Core In Computer
            posts - 139,  comments - 123,  trackbacks - 0

            [轉(zhuǎn)]Track'em Down...

            http://www.shnenglu.com/jerysun0818/archive/2006/06/04/8153.html



            P.S. 很多朋友都抱怨說STL出問題的時候debug很難,編譯期錯誤算是輕的,大不了一串串令人頭暈的出錯信息,至少還能雙擊定位到錯誤行。而神秘的運行期崩潰才是真正令人頭大的問題。下面就是一個比較典型的、五臟俱全的運行期崩潰事件,從幾行簡簡單單的代碼,似乎根本不可能崩潰,一直到最后揪出隱藏在背后的機制。其中的思維分析過程是怎樣的呢?希望對一些朋友有點幫助。標(biāo)題起作”Track’em Down”一方面是暗指整個分析跟蹤的過程,而是最后所揭露出的機制的確是個tracking機制:-)

            很久沒寫blog了,一來是諸事纏身,二來也是實在不像以前那么有熱情坐下來好好寫篇技術(shù)文章了。很多時候只是做個旁觀者,四處潛水而已。昨天又跟老婆鬧了矛盾,心里郁悶,于是給自己一個理由四處亂逛,跑到許久未去的cpper( http://www.cpper.com/c/ )上,cpper還是一如既往的冷清,看來一個論壇要想火起來光靠技術(shù)是不行的,cpper(前身為allaboutprogram)圈子里有一批技術(shù)很牛的朋友,也許是太牛了,這兩年都開始一個個牛得沒影兒了。二來cpper不像CSDN這樣有一個門戶,主頁上有亂七八糟的好玩信息,新聞,八卦,等等不一而足。國內(nèi)C++社群的大部分朋友想必是不知道cpper的,尤其是初學(xué)者。實在是個不小的遺憾…等等…似乎扯遠(yuǎn)了,剛才說到逛到cpper上,看到tomato的一個帖子,提到一段行為怪異的代碼,行為是崩潰,而且是在l析構(gòu)的時候崩潰:

            #include <list> ????????
            ??????????
            int main(int argc, char* argv[])
            {

            ? ? std::list<int> l;

            ? ? std::list<int>::iterator* it1 = new std::list<int>::iterator(l.begin());

            ? ? memset(it1, 1, sizeof(std::list<int>::iterator));

            ? ? l.push_back(1);

            ? ? l.begin();

            ? ? return0;

            以上是原貼里的代碼,我就不簡化了。大伙看著辦吧,呵呵。

            乍看上去這段代碼似乎不應(yīng)該有什么問題(至少不會崩潰)。最可疑的當(dāng)然是那行memset,但memset只是把it1指向的一個new出來的iterator給corrupt掉了,況且這個it1也沒被delete,所以也就不會因析構(gòu)而崩潰了(雖然有內(nèi)存泄漏,但tomato說的是崩潰),另外it1跟l看上去是井水不犯河水。所以怎么看不像有問題的樣子。

            以上是眼睜睜瞪著代碼看出來的結(jié)果,然而當(dāng)我把代碼塞到IDE里面,編譯運行之后卻發(fā)現(xiàn)果然如tomato所說,在list析構(gòu)時程序崩潰了(很湊巧我跟tomato用的IDE都是VC8,這是一個關(guān)鍵條件)。對于這類問題,一般我是先黑箱再白箱。這么短的程序,要出問題肯定出在memset那行,邏輯上可以大致這么說:“由于memset把it1的內(nèi)存corrupt掉了,結(jié)果導(dǎo)致l在析構(gòu)的時候崩潰。”聽到自己下這么個結(jié)論我都覺得有點哭笑不得,這壓根兒風(fēng)馬牛不相及嘛,it1被corrupt與l析構(gòu)崩潰有屁關(guān)系?!it1只不過是個指向l的迭代器,說穿了就是裹著個指向l中的某個node的指針而已,它的死活怎么會影響到l的析構(gòu)呢?

            但福爾摩斯大致說過,去掉那些絕對不可能的,剩下的就是可能。這里這是唯一的可能,只不過問題是,這個邏輯還不夠細(xì)致,需要豐滿起來。進(jìn)一步,要想使得l在析構(gòu)的時候崩潰,這個memset肯定以某種方式影響到了l。又因為memset直接影響的是it1,那么必然是it1和l的某種聯(lián)系導(dǎo)致memset間接對l產(chǎn)生了影響。于是再來看,it1和l有什么聯(lián)系呢?得!這下看出來了,it1在構(gòu)造的時候是通過l.begin()拷貝構(gòu)造的,哈!問題似乎有點眉目了,但到此我還是想不通,根據(jù)常識,iterator的拷貝構(gòu)造會產(chǎn)生出一個與源iterator不相干的副本,對這個副本干什么事怎么會影響到源呢?更別提影響到源iterator所指向的容器了。開玩笑!甭管他,既然已經(jīng)肯定下來這是唯一的可能,那么推論只能是:背后還隱藏著什么不為人之的東西。于是進(jìn)一步注釋掉“(l.begin())”,使it1變成缺省構(gòu)造的迭代器,果然,不再崩潰了。于是,現(xiàn)在可以肯定作出的結(jié)論是:“… it1 = new std::list<int>::iterator(l.begin());這行代碼使得it1所指向的迭代器與l發(fā)生了某些關(guān)系(當(dāng)然,不是it1指向l這樣的白癡關(guān)系)。很顯然,這個關(guān)系的建立點不可能是在l.begin(),因為這里還沒有涉及到it1,所以唯一的可能就是,這個關(guān)系是在std::list<int>::iterator的拷貝構(gòu)造函數(shù)階段發(fā)生的。這次拷貝構(gòu)造以l.begin()為參數(shù),而l.begin()推測起來應(yīng)當(dāng)帶了l的信息,因而it1就利用這個機會與l建立起了“某種關(guān)系”。OK,到目前為止這個“某種關(guān)系”只是一個模糊的概念,我們并不明確知道究竟是什么關(guān)系,竟然導(dǎo)致list析構(gòu)會失敗。看來是時候進(jìn)入白箱了,F(xiàn)5,break,看斷在什么地方了,說實話P.J Plauger寫的STL代碼至少在外觀上不像SGI的那么親近人,nevermind,我看到了我想看到的信息——一個名為_HAS_ITERATOR_DEBUGGING的宏,哈,想起來了,這一版的STL是帶有iterator debugging功能的,所以當(dāng)然能夠發(fā)現(xiàn)iterator被corrupt掉的情況。疑問來了,怎么發(fā)現(xiàn)的呢?直接的答案是,肯定有某種追蹤機制,而且根據(jù)前面的推理,由于是l在析構(gòu)的時候發(fā)生的崩潰(it1并不析構(gòu),因為并沒有對它進(jìn)行delete),所以l在析構(gòu)的時候必然能以某種方式訪問到it1并發(fā)現(xiàn)它被corrupt掉的事實,再推廣之,l肯定能夠訪問到所有指向它的迭代器(這才叫iterator debugging嘛),而it1在構(gòu)建的時候的確是拷貝構(gòu)造l.begin()來的,也就是說指向l,所以l必然應(yīng)當(dāng)知道(跟蹤)它(it1)的存在,又因為接下來的memset是個相當(dāng)low level/native的操作,所以l對此肯定不知情,還以為it1一直好端端的指著它的begin()處呢,最后當(dāng)它一個個察訪指向它內(nèi)部的迭代器,當(dāng)查訪到it1時就發(fā)現(xiàn)it1被corrupt掉了,于是崩潰。呼~一切到這里似乎都串起來了。剩下的就是去發(fā)現(xiàn)list<int>::iterator的拷貝構(gòu)造函數(shù)究竟在背后搗了什么鬼才使得l最終能夠訪問到it1的,在開始之前我就設(shè)想一個場景:肯定是l里面有一個鏈表,把所有指向它內(nèi)部的迭代器串起來了,這樣它就能夠逐一檢查這些迭代器,看看比如說它們是否越界之類的。而it1在拷貝構(gòu)造的時候由于是以l.begin()為參數(shù)的,l.begin()肯定帶有l(wèi)的信息,說白了,指向l的指針,或l的引用,于是it1的拷貝構(gòu)造函數(shù)就可以把自身鏈入l內(nèi)部的鏈表中去。想到這里似乎情況十分明朗了,剩下的就是跟蹤進(jìn)去驗證一下了…但是等一下…剛才說到,程序的現(xiàn)象是…崩潰!如果是在l對它的迭代器check的時候發(fā)現(xiàn)錯誤,大腦沒毛病的庫設(shè)計者肯定會拋出一個異常吧,不會是崩潰的癥狀…what the hell。反正我已經(jīng)知道問題的核心在list<int>::iterator的拷貝構(gòu)造函數(shù)那里,那是唯一同時擁有it1與l信息的地方。于是F11到list<int>::iterator的拷貝構(gòu)造處,即“… it1 = new std::list<int>::iterator(l.begin());”這行代碼處,郁悶的是,調(diào)試器在這里一跳而過,似乎它擁有的是一個trivial的拷貝構(gòu)造函數(shù)(從后來的結(jié)果來看這似乎是VC2005的一個小問題),而且我”go to disassembly”居然也就看到聊聊幾行代碼,除了一個對list<int>::begin()的調(diào)用之外就沒有其它調(diào)用了,傻眼了,似乎真的是個trivial的copy ctor?如果真是這樣的話,就不可能在這里建立起it1跟l之間的聯(lián)系了,因為trivial的copy constructing只會是把成員按位復(fù)制一下,沒有其它代碼,不涉及函數(shù)調(diào)用,怎么可能會有機會干其它事情呢?而后面又沒有任何地方同時涉及it1跟l的,那又怎么會最終導(dǎo)致l析構(gòu)崩潰呢?簡單的推理,反推上去,既然l最終析構(gòu)崩潰了,那么這個邏輯的某一環(huán)肯定錯了,最可能的就是,這并非trivial copy ctor,調(diào)試器欺騙了我的眼睛,背后肯定調(diào)用了某個拷貝構(gòu)造函數(shù),然后干了些勾當(dāng)。于是靜態(tài)跟蹤派上了用場,幸好VS2005的intelli-sense非常強大,兩次“go to definition”就置身于了list<int>::iterator的定義里面,剛才不是說關(guān)鍵就在list<int>::iterator的拷貝構(gòu)造函數(shù)里面嗎?于是瀏覽一下list<int>::iterator的定義,VC帶的STL的代碼真難看啊,好在這個類比較短,很遺憾,沒有拷貝構(gòu)造函數(shù),只有構(gòu)造函數(shù),不過碰巧,在其中一個由_HAS_ITERATOR_DEBUGGING條件控制的構(gòu)造函數(shù)里面發(fā)現(xiàn)了一行寶貴的代碼:this->_Adopt(_Plist);,如果沒猜錯,這肯定是把這個迭代器自身鏈到它所指向的容器內(nèi)的跟蹤鏈表中去的。_Plist(該構(gòu)造函數(shù)的第二個參數(shù))是什么?跟蹤到l.begin()調(diào)用里面就會發(fā)現(xiàn),喂給list<int>::iterator的構(gòu)造函數(shù)的第二個參數(shù)(也就是_Plist)是this,呵呵,看來this->_Adopt(_Plist)實際上就是把this(迭代器)“收養(yǎng)(adopt)”給_Plist啊。沒猜錯,果然是這樣的跟蹤機制。那么,照理說list<int>::iterator的拷貝構(gòu)造函數(shù)也應(yīng)該有相應(yīng)的動作才對啊,不然拷貝構(gòu)造出的新的iterator就會沒人“收養(yǎng)”了(而我們的跟蹤機制是應(yīng)當(dāng)跟蹤到每個“在世”的iterator的,否則就沒意義了)。剛才提到,list<int>::iterator本身沒有拷貝構(gòu)造函數(shù),那么只有一種可能,要么其成員具有non-trivial的拷貝構(gòu)造函數(shù),要么在基類里面。實際上list<int>::iterator只有一個成員,一如我們意料之中的,指向list的node的ptr。所以秘密肯定在基類中,順著基類一路找下去,_Const_iterator->_Bidit->_Iterator_base。說實話到_Bidit差點放棄,因為我以為_Bidit里面肯定就是一些typedef,就像unary_function那樣。事實卻不是這樣,下面還隱藏了一層_Iterator_base,而這個_Iterator_base就是一切秘密所在了。它是有拷貝構(gòu)造函數(shù)的,代碼我就不列了,如果你跟蹤到這里,真相也就大白了,簡單的來說,它根據(jù)源iterator找到其所指的容器,然后取出該容器里面的用于跟蹤迭代器的鏈表頭指針,然后把自身(this)鏈到這個鏈表里面去。由于一個iterator誕生的方式一共就兩種,一種是從某個容器誕生,這時是調(diào)用的它的一般構(gòu)造函數(shù),容器會把自身的指針當(dāng)作參數(shù)傳遞給這個iterator,后者通過這個容器指針來將其自身鏈接到容器的跟蹤鏈表中。第二個誕生方式就是這里說的,拷貝構(gòu)造,拷貝構(gòu)造時會將其自身鏈到源iterator所指向的容器內(nèi)的跟蹤鏈表中去。反正就是,一切現(xiàn)有的iterator都會恰當(dāng)被它所指的容器跟蹤著。

            那么是時候揭穿謎底了吧,為什么memset會闖下這么大的禍?因為一個iterator要被鏈到鏈表里面,它肯定有next指針,這樣才能鏈成一個鏈表嘛。而memset粗暴地將這個next指針給重置了(事實上它把it1指向的迭代器整個給memset了,當(dāng)然包括里面的next指針),這里,next指針被重置為了1(如果memset成0就不會崩潰了,原因很簡單,想想看),顯然,這就指向了一塊無意義的內(nèi)存。于是,l在析構(gòu)的時候,試圖遍歷并check它所跟蹤的iterator鏈表,隨著它通過next指針一節(jié)節(jié)跳轉(zhuǎn),當(dāng)?shù)搅宋覀兊膇t1的時候,由于其next指針的值是1,所以試圖再跳(next)的時候就非法內(nèi)存訪問了!崩潰!所以,這里的問題并不在于最后的check失敗了,而是在于迭代器鏈表被corrupt掉了,才導(dǎo)致的崩潰。這就解除了前面的關(guān)于未發(fā)生異常的疑惑。事情到此就大致結(jié)束了。

            當(dāng)然,這里還隱藏了很多的細(xì)節(jié)。例如最后l析構(gòu)時并不是要去“check”每個迭代器。另外這個iterator tracking的機制還是很有代表性的,boost::signal里面就用了如出一轍的手法,這個手法充分顯示出了C++的強大和靈活。原來看上去毫不起眼的構(gòu)造函數(shù)在背后還能做那么多工作。這里就不方方面面總結(jié)這一技術(shù)了,一是困了,二是這不是這篇blog的初衷,寫這篇blog一是為了平靜一下郁悶的心情,二是為了顯示一個從現(xiàn)象開始分析推理問題,最終接近答案的過程。程序員的大部分時間是在debug,這篇blog其實介紹的就相當(dāng)于一個debug的思維過程,或許它并不是最好的,但希望你能夠在其中發(fā)現(xiàn)一些有用的東西。

            P.S. 看上去啰啰嗦嗦一大通,實際上在腦子里轉(zhuǎn)來轉(zhuǎn)去是一瞬間的工夫,加上跟蹤(靜態(tài)/動態(tài))也就十來分鐘,而寫這篇blog倒花了我四十多分鐘(這也是越來越不寫blog的原因吧),人類自然語言的表達(dá)力某些時候是十分啰唆冗余的…

            posted on 2006-06-04 19:52 Jerry Cat 閱讀(327) 評論(0)  編輯 收藏 引用

            只有注冊用戶登錄后才能發(fā)表評論。
            網(wǎng)站導(dǎo)航: 博客園   IT新聞   BlogJava   博問   Chat2DB   管理



            <2006年7月>
            2526272829301
            2345678
            9101112131415
            16171819202122
            23242526272829
            303112345

            常用鏈接

            留言簿(7)

            隨筆檔案

            最新隨筆

            搜索

            •  

            最新評論

            閱讀排行榜

            評論排行榜

            色88久久久久高潮综合影院| 国产综合免费精品久久久| 国产精品九九久久精品女同亚洲欧美日韩综合区| 亚洲国产成人精品无码久久久久久综合 | 久久国产精品免费一区| 99久久精品国产高清一区二区| 久久婷婷人人澡人人爽人人爱| 久久久久亚洲国产| 久久人人爽人人爽AV片| 久久国产精品波多野结衣AV| 国产农村妇女毛片精品久久| 国产午夜精品理论片久久| 久久福利片| 久久性生大片免费观看性| 久久久久九国产精品| 色综合合久久天天给综看| 香港aa三级久久三级老师2021国产三级精品三级在 | 亚洲综合日韩久久成人AV| 无码人妻精品一区二区三区久久久| 久久久无码精品亚洲日韩京东传媒 | 久久久久久久久无码精品亚洲日韩 | 香蕉aa三级久久毛片| 精品久久久久成人码免费动漫| 久久久久亚洲AV无码观看| 精品国产一区二区三区久久久狼| 国产一级持黄大片99久久| 久久久久久亚洲精品不卡| 久久亚洲精品成人无码网站| 亚洲精品无码久久久久sm| 国内精品久久九九国产精品| 青青青青久久精品国产h久久精品五福影院1421 | 女同久久| 国产成人久久精品一区二区三区| 国产 亚洲 欧美 另类 久久| 思思久久99热只有频精品66| 久久久久久亚洲精品成人| 久久国产精品免费一区| 婷婷五月深深久久精品| 国产精久久一区二区三区| 午夜精品久久久久久中宇| 精品久久久久久久久久久久久久久|