就給我去死!
posted @
2007-07-29 20:29 shifan3 閱讀(786) |
評(píng)論 (7) |
編輯 收藏
我發(fā)現(xiàn)我的心情很難被影響,在高興的時(shí)候一些不好的小事我會(huì)直接無(wú)視,在不爽的時(shí)候也很難挽回低落的心情。所以壞處就是,雖然心情不容易變壞,但是一旦變壞,那就好幾天都好轉(zhuǎn)不了,不管是多么小的事。
這大概就是傳說(shuō)中的拿得起放不下。
嗯。今天一天的心情就被區(qū)區(qū)小事給毀了。不知道明天會(huì)不會(huì)忘掉。
多半還會(huì)繼續(xù)不爽。埃,幾天就畢業(yè)了,大家作鳥(niǎo)獸散,心中本應(yīng)有的是那種既高興又傷感,充滿了回憶和憧憬的情感,但是現(xiàn)在只剩下一肚子的
怨念。
posted @
2007-06-29 01:48 shifan3 閱讀(402) |
評(píng)論 (1) |
編輯 收藏
在本機(jī)上調(diào)試Linux內(nèi)核實(shí)在是慘烈,各種錯(cuò)誤都能出來(lái):
1。鍵盤無(wú)反應(yīng)
2。鼠標(biāo)無(wú)反應(yīng)
3。鍵盤鼠標(biāo)一起無(wú)反應(yīng)
4。屏幕突然黑掉
5。直接重啟動(dòng)
6。屏幕黑掉,數(shù)秒后出現(xiàn)一幅銀灰色圖片,數(shù)秒后重啟動(dòng)
7。進(jìn)程無(wú)法啟動(dòng)
8。不能切換到root
于是我一天重啟動(dòng)40+次,囧
posted @
2007-06-11 11:47 shifan3 閱讀(407) |
評(píng)論 (1) |
編輯 收藏
可以很明顯的感覺(jué)到,在不斷的努力下,校內(nèi)的本屆,以及下幾屆的技術(shù)圈,漸漸開(kāi)始接受boost, 接觸boost,甚至asio等等,boost以從未有過(guò)的高速,走進(jìn)了人們的視野。
看到這一切,很欣慰,也很有成就感。
posted @
2007-05-02 00:52 shifan3 閱讀(579) |
評(píng)論 (8) |
編輯 收藏
摘要: 平臺(tái) i386 , win32 , msvc 2003
代碼簡(jiǎn)單介紹:
調(diào)度算法:輪轉(zhuǎn)法。。,可修改
內(nèi)存模型:每個(gè)線程擁有各自獨(dú)立的堆棧。啟動(dòng)線程的時(shí)候,切換到對(duì)應(yīng)的堆棧再啟動(dòng),使得線程之間的堆棧互不干擾
調(diào)度方式:線程調(diào)用 schedule 函數(shù), schedule 用 setjmp 保存當(dāng)前堆棧,選擇一個(gè)...
閱讀全文
posted @
2007-03-16 16:33 shifan3 閱讀(3350) |
評(píng)論 (4) |
編輯 收藏
這家伙的書(shū)寫(xiě)的太牛了,而且太有創(chuàng)意了
posted @
2007-02-23 19:56 shifan3 閱讀(594) |
評(píng)論 (0) |
編輯 收藏
下面是腮邊打網(wǎng)的東西
1。學(xué)了一點(diǎn)點(diǎn)法語(yǔ)(處于會(huì)說(shuō)但聽(tīng)不懂的階段)
2。深入了解了佛教等幾大宗教,認(rèn)真學(xué)習(xí)了佛教的教義(不過(guò)仍然沒(méi)信教)
3。對(duì)西方古典音樂(lè)有了系統(tǒng)的學(xué)習(xí)
4。終于開(kāi)始用linux了,我真土
下面是技術(shù):
1。重新看了libtorrent的代碼
2。被迫研究Windows,ReactOS,wine和linux的源代碼,挖得有點(diǎn)感覺(jué)了
3。寫(xiě)了一堆范型的概念實(shí)現(xiàn)(typeof,multibyte什么的)
4。摸了摸boost.MPL,寫(xiě)了一個(gè)靜態(tài)狀態(tài)機(jī)(結(jié)果代碼不小心被誤刪了。。。)
5。用asio寫(xiě)了些東西,不過(guò)都是很土的程序
好像沒(méi)做什么事情唉
下面是階段性的
1。保研了
2。實(shí)習(xí)了
3。寫(xiě)書(shū)了
posted @
2007-02-04 15:45 shifan3 閱讀(607) |
評(píng)論 (3) |
編輯 收藏
說(shuō)到 C/C++ 的資源管理,人人都會(huì)頭痛半天。自從 C++0x (就是 C++09 了)標(biāo)準(zhǔn)漏出風(fēng)聲之后, C++ 標(biāo)準(zhǔn)是否會(huì)引入自動(dòng)垃圾回收機(jī)制就成為了眾多 C++ 愛(ài)好者談?wù)摰脑掝}。但是實(shí)際上,在 C++ 標(biāo)準(zhǔn)的探索上,垃圾回收一直處在一個(gè)十分低下的地位。造成其這一處境的原因很多,也很復(fù)雜。我們來(lái)看看站在 C++ 程序員的角度上看,資源管理機(jī)制現(xiàn)在所面臨的局勢(shì)。
從系統(tǒng)結(jié)構(gòu)上來(lái)講, C/C++ 支持 3 種內(nèi)存管理方式,基于棧的自動(dòng)管理,基于堆的動(dòng)態(tài)管理,和基于全局區(qū)的靜態(tài)管理。由于 RAII 的理念,對(duì)于 C++ 來(lái)說(shuō),內(nèi)存管理和其他資源管理本質(zhì)上沒(méi)有區(qū)別。因此對(duì)于資源而言,也就自然的擁有這樣 3 種管理方式。
首先簡(jiǎn)要的介紹一下 RAII 。 RAII 的全稱是 Resource Acquisition Is initialization 。這個(gè)思想的基本手法是對(duì)于一種想要使用的資源,為其書(shū)寫(xiě)一個(gè) guard 類,在該類的構(gòu)造函數(shù)里進(jìn)行資源的請(qǐng)求,在析構(gòu)函數(shù)里進(jìn)行資源的釋放。例如假設(shè)我們想管理一個(gè)互斥鎖,可能的方式是:
struct lock_guard

{

lock_guard()
{ lock ();}
~ lock_guard()
{unlock();}
} ;
此后,對(duì)這個(gè)對(duì)象使用什么內(nèi)存管理方式,也就等價(jià)于對(duì)這個(gè)互斥鎖使用什么內(nèi)存管理方式。
借助于 RAII ,以后我們可以只討論內(nèi)存資源的管理方式,其它資源的管理方式可以使用 RAII 來(lái)同樣的實(shí)現(xiàn)。現(xiàn)在我們已經(jīng)很自然的獲得了資源管理的 3 種方式:基于堆的動(dòng)態(tài)方式、基于棧的自動(dòng)方式和全局。值得一提的是,這 3 種方式中比較不容易出錯(cuò)的后兩種實(shí)際上可以解決大部分的資源管理需求。因?yàn)榻^大部分資源,都屬于獲取 - 使用 - 釋放型的,例如很多同步對(duì)象,文件鎖, WinGDI 里的許多 GDI 對(duì)象。我們?nèi)狈芾淼模挥心切┮淮潍@得,多個(gè)環(huán)境擁有,并且只能有一次釋放的少數(shù)資源。
回到內(nèi)存模型來(lái)看,有一點(diǎn)讓我們無(wú)法將內(nèi)存與其它資源等同(反過(guò)來(lái),把其它資源和內(nèi)存等同卻是可以的),那就是循環(huán)引用。 A 內(nèi)存可以持有指向 B 內(nèi)存的引用, B 內(nèi)存也可以反過(guò)來(lái)持有 A 內(nèi)存的引用。循環(huán)引用導(dǎo)致內(nèi)存管理不可以用“是否有指向該內(nèi)存的引用”來(lái)區(qū)分一塊內(nèi)存是否可以回收。從而喪失了一個(gè)絕佳的管理手段。但是在沒(méi)有循環(huán)引用的場(chǎng)合下,我們還是有非常簡(jiǎn)潔高效的管理方法的。那就是引用計(jì)數(shù)。
引用計(jì)數(shù)是在沒(méi)有循環(huán)引用場(chǎng)合下進(jìn)行內(nèi)存管理的絕佳手段,它具有輕量、高效、即時(shí)、可控的優(yōu)點(diǎn)。而且在 C++ 里,引用計(jì)數(shù)已經(jīng)非常成熟,只需要使用 boost.shared_ptr 或者其它非官方的引用計(jì)數(shù)指針庫(kù)就可以了,而且據(jù)悉 C++09 很可能把 boost.shared_ptr 納入標(biāo)準(zhǔn)庫(kù)。引用計(jì)數(shù)的原則是,如果一個(gè)對(duì)象沒(méi)有別的指針或引用來(lái)指向它,那么這個(gè)對(duì)象就是可以釋放的。具體的手法有大把大把的資料可以查閱,這里就不詳細(xì)說(shuō)明了。引用計(jì)數(shù)通常可以處理哪些場(chǎng)合的資源管理問(wèn)題呢?首先,對(duì)于單方向的資源管理,也就是多個(gè) A 的實(shí)體擁有 1 個(gè) B ,然而 B 并不會(huì)反過(guò)來(lái)依賴于 A (例如多個(gè)對(duì)象共享一個(gè)日志),引用計(jì)數(shù)是非常合適的。其次,對(duì)于擁有 - 反作用的場(chǎng)合,也就是 1 個(gè)或多個(gè) A 的實(shí)體擁有 1 個(gè)或多個(gè) B ,而 B 也擁有這些 A 的實(shí)體的引用,但是 B 的生存期仍然決定于 A 的生存期(例如父窗口擁有若干子窗口,子窗口也具有 parent 指針指向父窗口,但是子窗口的生存期決定于父窗口的生存期),這個(gè)時(shí)候 A 可以對(duì) B 使用引用計(jì)數(shù)指針,而 B 可以對(duì) A 使用原生的普通指針,同樣的可以很好的解決問(wèn)題。
現(xiàn)在所剩下的,就只有生存期的循環(huán)依賴了。如果 AB 互相持有對(duì)方的引用,而且 AB 互相的存在都依賴于對(duì)方,這樣引用計(jì)數(shù)就無(wú)法解決了。但是如果仔細(xì)想一下就會(huì)發(fā)現(xiàn),這種情況在 C++ 里幾乎不可能存在。生存期循環(huán)依賴只有 2 種后果,要么 A 和 B 的析構(gòu)函數(shù)里互相析構(gòu)(當(dāng)然就掛了),要么互相都不析構(gòu)(當(dāng)然就泄露了)。而這兩種都是在正常編程中不會(huì)出現(xiàn)的情況。所以如果即使僅僅使用引用計(jì)數(shù),我們也可以解決幾乎所有的資源管理問(wèn)題。
現(xiàn)在還剩下那么一丁點(diǎn)極少出現(xiàn)的不能處理的情況,我們可以使用更加復(fù)雜的 gc 來(lái)實(shí)現(xiàn)。可惜的是,實(shí)現(xiàn)一個(gè) gc 所要耗費(fèi)的精力實(shí)在太大,而且?guī)缀醪豢杀苊獾囊蔀榍秩胧降膸?kù)。所以有點(diǎn)得不償失。而且 gc 通常會(huì)產(chǎn)生更多的毛病:
1. 你無(wú)法卻知對(duì)象析構(gòu)的具體時(shí)間,從而無(wú)法真正知道影響程序性能的瓶頸在什么地方。
2. gc 都傾向于大量的使用內(nèi)存,直到內(nèi)存不夠的時(shí)候再進(jìn)行清理,這樣會(huì)導(dǎo)致程序的內(nèi)存用量嚴(yán)重顛簸,并且產(chǎn)生大量的換頁(yè)。
3. 過(guò)度的依賴于 gc 會(huì)使程序員大量的把可以由之前提到的各種方法來(lái)處理的資源交給 gc 來(lái)處理,無(wú)故的加重了 gc 的負(fù)擔(dān)。
4. gc 的管理方法和 C++ 的析構(gòu)函數(shù)有可能產(chǎn)生語(yǔ)義上的沖突。
這就是為什么 C++ 標(biāo)準(zhǔn)對(duì)垃圾回收的態(tài)度如此惡劣的原因。
我們現(xiàn)在回過(guò)頭來(lái)看 Java/C# 這樣的內(nèi)置 gc 的語(yǔ)言。這樣的語(yǔ)言由于使用了 gc ,就不可避免的放棄了析構(gòu)函數(shù)。為什么 gc 會(huì)和析構(gòu)函數(shù)產(chǎn)生沖突呢?一個(gè) gc 一般會(huì)希望在進(jìn)行垃圾回收的時(shí)候,整個(gè)過(guò)程是一個(gè)原子的,但析構(gòu)函數(shù)會(huì)破壞這一點(diǎn),在釋放內(nèi)存的時(shí)候如果還要執(zhí)行代碼,那么難免會(huì)對(duì)整個(gè) gc 環(huán)境產(chǎn)生破壞性的影響。由于沒(méi)有析構(gòu)函數(shù),這些語(yǔ)言就不可能做到 RAII ,也就是說(shuō),它們的 gc 所能夠管理的,也就僅僅只有內(nèi)存而已了。對(duì)于其他資源, Java 等就必須手動(dòng)釋放。雖然 C# 提供了 with 關(guān)鍵字來(lái)緩解這一問(wèn)題,但仍然無(wú)法徹底的解決。
還有什么麻煩呢?之前說(shuō)的那 4 點(diǎn)全部都有。雖然 JVM 的速度在不斷的提高,但是內(nèi)存使用這一點(diǎn)卻完全沒(méi)有發(fā)展,不能不說(shuō)是 gc 說(shuō)導(dǎo)致。它所帶來(lái)了什么好處呢?是內(nèi)存管理的自動(dòng)化,而不是資源管理的自動(dòng)化。
所以說(shuō) C++ 并不是世人所想象的那樣需要 gc , C++ 本身就已經(jīng)提供了足夠強(qiáng)大的資源管理能力。基于棧的自動(dòng)管理,或者使用引用計(jì)數(shù),幾乎可以達(dá)到和 gc 同樣的覆蓋面,而且沒(méi)有 gc 的那些問(wèn)題, RAII 使得 C++ 在管理非內(nèi)存資源的時(shí)候還更加有優(yōu)勢(shì),為什么不使用呢?
ps. 設(shè)計(jì)一個(gè)非官方的 gc 庫(kù)還是可以的。但是畢竟不會(huì)成為主流了。
posted @
2007-01-24 18:02 shifan3 閱讀(2162) |
評(píng)論 (4) |
編輯 收藏
發(fā)信人:?ufoer?(我有一雙黑色的眼睛),?板面:?Religion
標(biāo)??題:?[轉(zhuǎn)載]?人生三重境界
發(fā)信站:?飄渺水云間?(Sat?Jan?13?13:22:14?2007),?轉(zhuǎn)信
【?原文由?abbr?發(fā)表于?ZJUOnline?討論區(qū)?】???????????????????????????????????????????????????????????????????????????????
人生有三重境界,這三重境界可以用一段充滿禪機(jī)的語(yǔ)言來(lái)說(shuō)明,這段語(yǔ)言便是:
看山是山,看水是水;
看山不是山,看水不是水;
看山還是山。看水還是水。
這就是說(shuō)一個(gè)人的人生之初純潔無(wú)瑕,初識(shí)世界,一切都是新鮮的,眼睛看見(jiàn)什
么就是什么,人家告訴他這是山,他就認(rèn)識(shí)了山;告訴他這是水,他就認(rèn)識(shí)了水。
隨著年齡漸長(zhǎng),經(jīng)歷的世事漸多,就發(fā)現(xiàn)這個(gè)世界的問(wèn)題了。這個(gè)世界問(wèn)題越來(lái)
越多,越來(lái)越復(fù)雜,經(jīng)常是黑白顛倒,是非混淆,無(wú)理走遍天下,有理寸步難行,好
人無(wú)好報(bào),惡人活千年。進(jìn)人這個(gè)階段,人是激憤的,不平的,憂慮的,疑問(wèn)的,警
惕的,復(fù)雜的。人不愿意再輕易地相信什么。人到了這個(gè)時(shí)候看山也感慨,看水也嘆
息,借古諷今,指桑罵槐。山自然不再是單純的山,水自然不再是單純的水。一切的
一切都是人的主觀意志的載體,所謂“好風(fēng)憑借力,送我上青云”。一個(gè)人倘若停留
在人生的這一?階段,那就苦了這條性命了。人就會(huì)這山望了那山高,不停地攀登,
爭(zhēng)強(qiáng)好勝,與人比較,怎么做人,如何處世,絞盡腦汁,機(jī)關(guān)算盡,永無(wú)休止和滿足
的一天。因?yàn)檫@個(gè)世界原本就是一個(gè)圓的,人外還有人,天外還有天,循環(huán)往復(fù),綠
水長(zhǎng)流。而人的生命是短暫的有限的,哪里能夠去與永恒和無(wú)限計(jì)較呢?
許多人到了人生的第二重境界就到了人生的終點(diǎn)。追求一生.勞碌一生,心高氣
傲一生,最后發(fā)現(xiàn)自己并沒(méi)有達(dá)到自己的理想,于是抱恨終生。但是有些人通過(guò)自己
的修練,終于把自己提升到了第三重人生境界。茅塞頓開(kāi),回歸自然。人這個(gè)時(shí)候便
會(huì)專心致志做自己應(yīng)該做的事情,不與旁人有任何計(jì)較。任你紅塵滾滾,我自清風(fēng)朗
月。面對(duì)?蕪雜世俗之事,一笑了之,了了有何不了,這個(gè)時(shí)候的人看山又是山,看
水又是水了。正是:人本是人,不必刻意去做人;世本是世,無(wú)須精心去處世;便也
就是真正的做人與處世了。
--
隕石疾馳的絢爛會(huì)消逝在無(wú)盡的夜空
但恒星的光輝依舊閃耀在蒼穹
明星顯赫的聲名將淡出于大眾的記憶
而他們的旋律永遠(yuǎn)被靈魂傳誦
※?來(lái)源:·飄渺水云間?freecity.cn·[FROM:?beyondgenius]??????????????????????????????????????????????????????????????????
--
※?轉(zhuǎn)載:·飄渺水云間?freecity.cn·[FROM:?abbr]??????????????????????????????????????????????????????????????????????????
--
※?轉(zhuǎn)載:·飄渺水云間?freecity.cn·[FROM:?ufoer]?????????????????????????????????????????????????????????????????????????
posted @
2007-01-13 14:33 shifan3 閱讀(613) |
評(píng)論 (3) |
編輯 收藏
詳解link
有些人寫(xiě)C/C++(以下假定為C++)程序,對(duì)unresolved external link或者duplicated external simbol的錯(cuò)誤信息不知所措(因?yàn)檫@樣的錯(cuò)誤信息不能定位到某一行)。或者對(duì)語(yǔ)言的一些部分不知道為什么要(或者不要)這樣那樣設(shè)計(jì)。了解本文之后,或許會(huì)有一些答案。
首先看看我們是如何寫(xiě)一個(gè)程序的。如果你在使用某種IDE(Visual Studio,Elicpse,Dev C++等),你可能不會(huì)發(fā)現(xiàn)程序是如何組織起來(lái)的(很多人因此而反對(duì)初學(xué)者使用IDE)。因?yàn)槭褂肐DE,你所做的事情,就是在一個(gè)項(xiàng)目里新建一系列的.cpp和.h文件,編寫(xiě)好之后在菜單里點(diǎn)擊“編譯”,就萬(wàn)事大吉了。但其實(shí)以前,程序員寫(xiě)程序不是這樣的。他們首先要打開(kāi)一個(gè)編輯器,像編寫(xiě)文本文件一樣的寫(xiě)好代碼,然后在命令行下敲
cc 1.cpp -o 1.o
cc 2.cpp -o 2.o
cc 3.cpp -o 3.o
這里cc代表某個(gè)C/C++編譯器,后面緊跟著要編譯的cpp文件,并且以-o指定要輸出的文件(請(qǐng)?jiān)徫覜](méi)有使用任何一個(gè)流行編譯器作為例子)。這樣當(dāng)前目錄下就會(huì)出現(xiàn):
1.o 2.o 3.o
最后,程序員還要鍵入
link 1.o 2.o 3.o -o a.out
來(lái)生成最終的可執(zhí)行文件a.out。現(xiàn)在的IDE,其實(shí)也同樣遵照著這個(gè)步驟,只不過(guò)把一切都自動(dòng)化了。
讓我們來(lái)分析上面的過(guò)程,看看能發(fā)現(xiàn)什么。
首先,對(duì)源代碼進(jìn)行編譯,是對(duì)各個(gè)cpp文件單獨(dú)進(jìn)行的。對(duì)于每一次編譯,如果排除在cpp文件里include別的cpp文件的情況(這是C++代碼編寫(xiě)中極其錯(cuò)誤的寫(xiě)法),那么編譯器僅僅知道當(dāng)前要編譯的那一個(gè)cpp文件,對(duì)其他的cpp文件的存在完全不知情。
其次,每個(gè)cpp文件編譯后,產(chǎn)生的.o文件,要被一個(gè)鏈接器(link)所讀入,才能最終生成可執(zhí)行文件。
好了,有了這些感性認(rèn)識(shí)之后,讓我們來(lái)看看C/C++程序是如何組織的。
首先要知道一些概念:
編譯:編譯器對(duì)源代碼進(jìn)行編譯,是將以文本形式存在的源代碼翻譯為機(jī)器語(yǔ)言形式的目標(biāo)文件的過(guò)程。
編譯單元:對(duì)于C++來(lái)說(shuō),每一個(gè)cpp文件就是一個(gè)編譯單元。從之前的編譯過(guò)程的演示可以看出,各個(gè)編譯單元之間是互相不可知的。
目標(biāo)文件:由編譯所生成的文件,以機(jī)器碼的形式包含了編譯單元里所有的代碼和數(shù)據(jù),以及一些其他的信息。
下面我們具體看看編譯的過(guò)程。我們跳過(guò)語(yǔ)法分析等,直接來(lái)到目標(biāo)文件的生成。假設(shè)我們有一個(gè)1.cpp文件
int n = 1;
void f()
{
++n;
}
它編譯出來(lái)的目標(biāo)文件1.o就會(huì)有一個(gè)區(qū)域(假定名稱為2進(jìn)制段),包含了以上數(shù)據(jù)/函數(shù),其中有n, f,以文件偏移量的形式給出很可能就是:
偏移量 內(nèi)容 長(zhǎng)度
0x000 n 4
0x004 f ??
注意:這僅僅是猜測(cè),不代表目標(biāo)文件的真實(shí)布局。目標(biāo)文件的各個(gè)數(shù)據(jù)不一定連續(xù),也不一定按照這個(gè)順序,當(dāng)然也不一定從0x000開(kāi)始。
現(xiàn)在我們看看從0x004開(kāi)始f函數(shù)的內(nèi)容(在0x86平臺(tái)下的猜測(cè)):
0x004 inc DWORD PTR [0x000]
0x00? ret
注意n++已經(jīng)被翻譯為:inc DWORD PTR [0x000],也就是把本單元0x000位置上的一個(gè)DWORD(4字節(jié))加1。
下面如果有另一個(gè)2.cpp,如下
extern int n;
void g()
{
++n;
}
那么它的目標(biāo)文件2.o的2進(jìn)制段就應(yīng)該是
偏移量 內(nèi)容 長(zhǎng)度
0x000 g ??
為什么這里沒(méi)有n的空間(也就是n的定義),因?yàn)閚被聲明為extern,表明n的定義在別的編譯單元里。別忘了編譯的時(shí)候是不可能知道別的編譯單元的情況的,故編譯器不知道n究竟在何處,所以這個(gè)時(shí)候g的二進(jìn)制代碼里沒(méi)有辦法填寫(xiě)inc DWORD PTR [???]中的???部分。怎么辦呢?這個(gè)工作就只能交給后來(lái)的鏈接器去處理。為了讓鏈接器知道哪些地方的地址是沒(méi)有填好的,所以目標(biāo)文件還要有一個(gè)“未解決符號(hào)表”,也就是unresolved symbol table. 同樣,提供n的定義的目標(biāo)文件(也就是1.o)也要提供一個(gè)“導(dǎo)出符號(hào)表”,export symbol table, 來(lái)告訴鏈接器自己可以提供哪些地址。
讓我們理一下思路:現(xiàn)在我們知道,每一個(gè)目標(biāo)文件,除了擁有自己的數(shù)據(jù)和二進(jìn)制代碼之外,還要至少提供2個(gè)表:未解決符號(hào)表和導(dǎo)出符號(hào)表,分別告訴鏈接器自己需要什么和能夠提供什么。下面的問(wèn)題是,如何在2個(gè)表之間建立對(duì)應(yīng)關(guān)系。這里就有一個(gè)新的概念:符號(hào)。在C/C++中,每一個(gè)變量和函數(shù)都有自己的符號(hào)。例如變量n的符號(hào)就是“n”。函數(shù)的符號(hào)要更加復(fù)雜,它需要結(jié)合函數(shù)名及其參數(shù)和調(diào)用慣例等,得到一個(gè)唯一的字符串。f的符號(hào)可能就是"_f"(根據(jù)不同編譯器可以有變化)。
所以,1.o的導(dǎo)出符號(hào)表就是
符號(hào) 地址
n 0x000
_f 0x004
而未解決符號(hào)表為空
2.o的導(dǎo)出符號(hào)表為
符號(hào) 地址
_g 0x000
未解決符號(hào)表為
符號(hào) 地址
n 0x001
這里0x001為從0x000開(kāi)始的inc DWORD PTR [???]的二進(jìn)制編碼中存儲(chǔ)???的起始地址(這里假設(shè)inc的機(jī)器碼的第2-5字節(jié)為要+1的絕對(duì)地址,需要知道確切情況可查手冊(cè))。這個(gè)表告訴鏈接器,在本編譯單元0x001的位置上有一個(gè)地址,該地址值不明,但是具有符號(hào)n。
鏈接的時(shí)候,鏈接器在2.o里發(fā)現(xiàn)了未解決符號(hào)n,那么在查找所有編譯單元的時(shí)候,在1.o中發(fā)現(xiàn)了導(dǎo)出符號(hào)n,那么鏈接器就會(huì)將n的地址0x000填寫(xiě)到2.o的0x001的位置上。
“打住”,可能你就會(huì)跳出來(lái)指責(zé)我了。如果這樣做得話,豈不是g的內(nèi)容就會(huì)變成inc DWORD PTR [0x000],按照之前的理解,這是將本單元的0x000地址的4字節(jié)加1,而不是將1.o的對(duì)應(yīng)位置加1。是的,因?yàn)槊總€(gè)編譯單元的地址都是從0開(kāi)始的,所以最終拼接起來(lái)的時(shí)候地址會(huì)重復(fù)。所以鏈接器會(huì)在拼接的時(shí)候?qū)Ω鱾€(gè)單元的地址進(jìn)行調(diào)整。這個(gè)例子中,假設(shè)2.o的0x00000000地址被定位在可執(zhí)行文件的0x00001000上,而1.o的0x00000000地址被定位在可執(zhí)行文件的0x00002000上,那么實(shí)際上對(duì)鏈接器來(lái)說(shuō),1.o的導(dǎo)出符號(hào)表其實(shí)
符號(hào) 地址
n 0x000 + 0x2000
_f 0x004 + 0x2000
而未解決符號(hào)表為空
2.o的導(dǎo)出符號(hào)表為
符號(hào) 地址
_g 0x000 + 0x1000
未解決符號(hào)表為
符號(hào) 地址
n 0x001 + 0x1000
所以最終g的代碼會(huì)變?yōu)閕nc DWORD PTR [0x000 + 0x2000]。
最后還有一個(gè)漏洞,既然最后n的地址變?yōu)?x2000了,那么以前f的代碼inc DWORD PTR [0x000]就是錯(cuò)誤的了。所以目標(biāo)文件為此還要提供一個(gè)表,叫做地址重定向表address redirect table。
對(duì)于1.o來(lái)說(shuō),它的重定向表為
地址
0x005
這個(gè)表不需要符號(hào),當(dāng)鏈接器處理這個(gè)表的時(shí)候,發(fā)現(xiàn)地址為0x005的位置上有一個(gè)地址需要重定向,那么直接在以0x005開(kāi)始的4個(gè)字節(jié)上加上0x2000就可以了。
讓我們總結(jié)一下:編譯器把一個(gè)cpp編譯為目標(biāo)文件的時(shí)候,除了要在目標(biāo)文件里寫(xiě)入cpp里包含的數(shù)據(jù)和代碼,還要至少提供3個(gè)表:未解決符號(hào)表,導(dǎo)出符號(hào)表和地址重定向表。
未解決符號(hào)表提供了所有在該編譯單元里引用但是定義并不在本編譯單元里的符號(hào)及其出現(xiàn)的地址。
導(dǎo)出符號(hào)表提供了本編譯單元具有定義,并且愿意提供給其他編譯單元使用的符號(hào)及其地址。
地址重定向表提供了本編譯單元所有對(duì)自身地址的引用的記錄。
鏈接器進(jìn)行鏈接的時(shí)候,首先決定各個(gè)目標(biāo)文件在最終可執(zhí)行文件里的位置。然后訪問(wèn)所有目標(biāo)文件的地址重定向表,對(duì)其中記錄的地址進(jìn)行重定向(即加上該編譯單元實(shí)際在可執(zhí)行文件里的起始地址)。然后遍歷所有目標(biāo)文件的未解決符號(hào)表,并且在所有的導(dǎo)出符號(hào)表里查找匹配的符號(hào),并在未解決符號(hào)表中所記錄的位置上填寫(xiě)實(shí)際的地址(也要加上擁有該符號(hào)定義的編譯單元實(shí)際在可執(zhí)行文件里的起始地址)。最后把所有的目標(biāo)文件的內(nèi)容寫(xiě)在各自的位置上,再作一些別的工作,一個(gè)可執(zhí)行文件就出爐了。
最終link 1.o 2.o .... 所生成的可執(zhí)行文件大概是
0x00000000 ????(別的一些信息)
....
0x00001000 inc DWORD PTR [0x00002000] //這里是2.o的開(kāi)始,也就是g的定義
0x00001005 ret //假設(shè)inc為5個(gè)字節(jié),這里是g的結(jié)尾
....
0x00002000 0x00000001 //這里是1.o的開(kāi)始,也是n的定義(初始化為1)
0x00002004 inc DWORD PTR [0x00002000] //這里是f的開(kāi)始
0x00002009 ret //假設(shè)inc為5個(gè)字節(jié),這里是f的結(jié)尾
...
...
實(shí)際鏈接的時(shí)候更為復(fù)雜,因?yàn)閷?shí)際的目標(biāo)文件里把數(shù)據(jù)/代碼分為好幾個(gè)區(qū),重定向等要按區(qū)進(jìn)行,但原理是一樣的。
現(xiàn)在我們可以來(lái)看看幾個(gè)經(jīng)典的鏈接錯(cuò)誤了:
unresolved external link..
這個(gè)很顯然,是鏈接器發(fā)現(xiàn)一個(gè)未解決符號(hào),但是在導(dǎo)出符號(hào)表里沒(méi)有找到對(duì)應(yīng)的項(xiàng)。
解決方案么,當(dāng)然就是在某個(gè)編譯單元里提供這個(gè)符號(hào)的定義就行了。(注意,這個(gè)符號(hào)可以是一個(gè)變量,也可以是一個(gè)函數(shù)),也可以看看是不是有什么該鏈接的文件沒(méi)有鏈接
duplicated external simbols...
這個(gè)則是導(dǎo)出符號(hào)表里出現(xiàn)了重復(fù)項(xiàng),因此鏈接器無(wú)法確定應(yīng)該使用哪一個(gè)。這可能是使用了重復(fù)的名稱,也可能有別的原因。
我們?cè)賮?lái)看看C/C++語(yǔ)言里針對(duì)這一些而提供的特性:
extern:這是告訴編譯器,這個(gè)符號(hào)在別的編譯單元里定義,也就是要把這個(gè)符號(hào)放到未解決符號(hào)表里去。(外部鏈接)
static:如果該關(guān)鍵字位于全局函數(shù)或者變量的聲明的前面,表明該編譯單元不導(dǎo)出這個(gè)函數(shù)/變量的符號(hào)。因此無(wú)法在別的編譯單元里使用。(內(nèi)部鏈接)。如果是static局部變量,則該變量的存儲(chǔ)方式和全局變量一樣,但是仍然不導(dǎo)出符號(hào)。
默認(rèn)鏈接屬性:對(duì)于函數(shù)和變量,模認(rèn)外部鏈接,對(duì)于const變量,默認(rèn)內(nèi)部鏈接。(可以通過(guò)添加extern和static改變鏈接屬性)
外部鏈接的利弊:外部鏈接的符號(hào),可以在整個(gè)程序范圍內(nèi)使用(因?yàn)閷?dǎo)出了符號(hào))。但是同時(shí)要求其他的編譯單元不能導(dǎo)出相同的符號(hào)(不然就是duplicated external simbols)
內(nèi)部鏈接的利弊:內(nèi)部鏈接的符號(hào),不能在別的編譯單元內(nèi)使用。但是不同的編譯單元可以擁有同樣名稱的內(nèi)部鏈接符號(hào)。
為什么頭文件里一般只可以有聲明不能有定義:頭文件可以被多個(gè)編譯單元包含,如果頭文件里有定義,那么每個(gè)包含這個(gè)頭文件的編譯單元就都會(huì)對(duì)同一個(gè)符號(hào)進(jìn)行定義,如果該符號(hào)為外部鏈接,則會(huì)導(dǎo)致duplicated external simbols。因此如果頭文件里要定義,必須保證定義的符號(hào)只能具有內(nèi)部鏈接。
為什么常量默認(rèn)為內(nèi)部鏈接,而變量不是:
這就是為了能夠在頭文件里如const int n = 0這樣的定義常量。由于常量是只讀的,因此即使每個(gè)編譯單元都擁有一份定義也沒(méi)有關(guān)系。如果一個(gè)定義于頭文件里的變量擁有內(nèi)部鏈接,那么如果出現(xiàn)多個(gè)編譯單元都定義該變量,則其中一個(gè)編譯單元對(duì)該變量進(jìn)行修改,不會(huì)影響其他單元的同一變量,會(huì)產(chǎn)生意想不到的后果。
為什么函數(shù)默認(rèn)是外部鏈接:
雖然函數(shù)是只讀的,但是和變量不同,函數(shù)在代碼編寫(xiě)的時(shí)候非常容易變化,如果函數(shù)默認(rèn)具有內(nèi)部鏈接,則人們會(huì)傾向于把函數(shù)定義在頭文件里,那么一旦函數(shù)被修改,所有包含了該頭文件的編譯單元都要被重新編譯。另外,函數(shù)里定義的靜態(tài)局部變量也將被定義在頭文件里。
為什么類的靜態(tài)變量不可以就地初始化:所謂就地初始化就是類似于這樣的情況:
class A
{
static char msg[] = "aha";
};
不允許這樣做得原因是,由于class的聲明通常是在頭文件里,如果允許這樣做,其實(shí)就相當(dāng)于在頭文件里定義了一個(gè)非const變量。
在C++里,頭文件定義一個(gè)const對(duì)象會(huì)怎么樣:
一般不會(huì)怎么樣,這個(gè)和C里的在頭文件里定義const int一樣,每一個(gè)包含了這個(gè)頭文件的編譯單元都會(huì)定義這個(gè)對(duì)象。但由于該對(duì)象是const的,所以沒(méi)什么影響。但是:有2種情況可能破壞這個(gè)局面:
1。如果涉及到對(duì)這個(gè)const對(duì)象取地址并且依賴于這個(gè)地址的唯一性,那么在不同的編譯單元里,取到的地址可以不同。(但一般很少這么做)
2。如果這個(gè)對(duì)象具有mutable的變量,某個(gè)編譯單元對(duì)其進(jìn)行修改,則同樣不會(huì)影響到別的編譯單元。
為什么類的靜態(tài)常量也不可以就地初始化:
因?yàn)檫@相當(dāng)于在頭文件里定義了const對(duì)象。作為例外,int/char等可以進(jìn)行就地初始化,是因?yàn)檫@些變量可以直接被優(yōu)化為立即數(shù),就和宏一樣。
內(nèi)聯(lián)函數(shù):
C++里的內(nèi)聯(lián)函數(shù)由于類似于一個(gè)宏,因此不存在鏈接屬性問(wèn)題。
為什么公共使用的內(nèi)聯(lián)函數(shù)要定義于頭文件里:
因?yàn)榫幾g時(shí)編譯單元之間互相不知道,如果內(nèi)聯(lián)函數(shù)被定義于.cpp文件中,編譯其他使用該函數(shù)的編譯單元的時(shí)候沒(méi)有辦法找到函數(shù)的定義,因此無(wú)法對(duì)函數(shù)進(jìn)行展開(kāi)。所以說(shuō)如果內(nèi)聯(lián)函數(shù)定義于.cpp文件里,那么就只有這個(gè)cpp文件可以是用這個(gè)函數(shù)。
頭文件里內(nèi)聯(lián)函數(shù)被拒絕會(huì)怎樣:
如果定義于頭文件里的內(nèi)聯(lián)函數(shù)被拒絕,那么編譯器會(huì)自動(dòng)在每個(gè)包含了該頭文件的編譯單元里定義這個(gè)函數(shù)并且不導(dǎo)出符號(hào)。
如果被拒絕的內(nèi)聯(lián)函數(shù)里定義了靜態(tài)局部變量,這個(gè)變量會(huì)被定義于何處:
早期的編譯器會(huì)在每個(gè)編譯單元里定義一個(gè),并因此產(chǎn)生錯(cuò)誤的結(jié)果,較新的編譯器會(huì)解決這個(gè)問(wèn)題,手段未知。
為什么export關(guān)鍵字沒(méi)人實(shí)現(xiàn):
export要求編譯器跨編譯單元查找函數(shù)定義,使得編譯器實(shí)現(xiàn)非常困難。
編譯和靜態(tài)鏈接就分析到這里,我會(huì)帶著動(dòng)態(tài)鏈接和load的詳解殺回來(lái)
posted @
2007-01-05 16:03 shifan3 閱讀(6318) |
評(píng)論 (13) |
編輯 收藏