乙個小程式背後的故事 分段

2021-06-05 05:09:45 字數 4249 閱讀 8565

現在大家都在談設計,談架構,談框架,套用一句流行語:「 

雖然聽不懂他們在說什麼,但是感覺很牛的樣子。 」

誠然。在軟體日益龐大的今天,先古時代單打獨鬥的天才程式設計師幾乎銷聲匿跡了,程式設計師的成功大多是團隊的成功,團隊合作無非也是分層,分模組,架構設計完了就是用各種框架,高內聚,低耦合云云。

很少有人聽到有人在討論編譯,連線,演算法,資料結構之類的東西。

程式設計師越來越像軟體集成員了。

我還是喜歡自下而上的學習方法。前天看了本書《程式設計師的自我修養——

編譯,連線和庫》。又明白了一些底層的東西。準確講應該是作業系統和高階語言(應用程式)之間那層。這本書很好。(只有在買書的時候,我才能深刻的體會到知識就是金錢。

65塊錢啊。才

450多頁。)大一的時候借了,

「 雖然看不懂作者在講什麼,但是感覺很牛的樣子

「,大二的時候也借了,依舊是

「 雖然看不懂作者在講什麼,但是感覺很牛的樣子

」,前幾天在圖書館溜達。又一次和它邂逅。遂收入囊中,借來看看

。雖然不至於看懂,但令我欣慰的是終於知道作者是講什麼的了。

初中時候覺得,寫軟體的人好厲害啊。高中的時候覺得,寫作業系統的人好厲害啊。大一的時候覺得,寫編譯器的人真牛啊。大二的時候想,發明語言的才是真正的大牛。現在想想,只能對以前的想法呵呵一笑。

john resig

22歲寫了

jquery 

。唉~ 

各種***。

先說一點編譯的東西吧。

編譯過程:

1.預編譯

prepressing

展開巨集#define

,載入庫檔案

#include

等。2.

編譯compilation 

高階語言編譯成組合語言,這個過程會有編譯器優化。

3.彙編

assembly

組合語言編譯成二進位制**。這個過程是最簡單的。

4.連線

linking

位址和空間分配,符號決議,重定位。

個人覺得編譯和連線過程是最複雜的。

編譯:因為現代cpu

已經很繁雜,各種功能很多。暫且不說

cisc 

和 risc 

。x86

,x86_64

,ia64

,mips

,arm

等等架構的

cpu,還有亂序發射,流水線優化,多核優化,暫存器優化,

cacha

優化等等一堆亂七八糟的東西。可謂眼花繚亂。記得

gcc支援

50+種

cpu平台的編譯。恐怖的一公尺。亂序發射,編譯器,

cache

優化等各種

cpu的特性,也增加了程式併發多執行緒編寫的難度。

連線:動態連線,靜態連線,可重入,位址重定向,個人感覺也是比較難的東西。

再穿插點x86

和彙編的東西。

8086

處理器有

20跟位址線,但是只有

16跟資料線。所以可以定位

1m的記憶體空間,但是只能訪問

64k的位址,為了解決這個問題,

intel 

的工程師很聰明的用了乙個方法,段

+偏移量 這種記憶體訪問方式。寫彙編的時候,一般是這樣開頭的。

code segment,

data segment

,stack segment 

三個段。我以前一直以為沒有作業系統的時候,這三個段就依次匯入到

8086

的三個段裡面。真無知啊。

馮諾依曼果然是大神。沒有計算機的時候就設計了計算機的靈魂。資料和指令分離。試想一下在混沌初開的時候,如果資料和指令是混雜在一起的話,那將是多麼恐怖的事情。不過只要是乙個正常的人,寫彙編的時候就會很自然的把指令和資料分離。

假如不分離的話,乙個指令執行完了,後面乙個是資料,要怎麼區分?cpu

會不會把資料當做指令去執行?

解決方法之一是記憶體裡面加標誌位,0

是可執行指令,

1是不可執行的資料。無疑這會造成記憶體浪費。

另一種方法是按照 指令-資料-

指令-資料-

指令-資料 交叉存放(當然,也可以1指令

n資料,或者n指令

1資料)方式存放,

cpu根據週期來判斷記憶體內容是資料還是指令,這會出現很嚴重的問題,

1.程式指令和資料的比例不是固定的。

2.又會增加

cpu

上面乙個問題就很麻煩了,姑且放在一邊不考慮,如果程式設計師無意寫錯了指令,跳轉到了乙個存放資料的記憶體或者不小心修改了指令(現在的指令都是唯讀的,這也就是為什麼每次除錯的時候要編譯整個程式)怎麼辦,那從那以後所有的程式都是壞死的(起碼是不可預料的),現在我們寫程式,錯一句就那麼一句錯了,不會影響到剩下的,如果一句錯了,剩下的都跟著亂了,那將是多麼恐怖的事情。

所以,乙個正常的人會把指令和資料分離的。馮大神果然是大神。

指令和資料混雜的壞處可謂多多,指令和資料分離有什麼好處呢?可也是謂多多。 1.

現代cpu

的cache

都是指令

cache

和資料cache

分離的,想象一下,資料和指令分離了,指令

cache

和資料cache

也分離了,那麼指令和資料的

cache

命中率一下子提高了

50%,乙個小時的程式半個小時就能執行完。這還沒算上面兩個問題的記憶體節約以及相容性問題。

2.指令是唯讀的,避免了程式設計師無意識地對指令區域記憶體的修改。避免產生了很多錯誤。

3.指令和資料分離,所以指令可以重複利用。也就是後面將要討論的靜態連線以及動態鏈結的東西。

綜上所述,計算機要指令和資料分離。我估計馮大神的**也就這麼寫的內容吧。改天到網上找來膜拜一下。

馮骨開天之後,程式猿們看到了光明。沐浴在神的思想之中,程式猿們感到了快樂。可是有些程式猿不滿足於神既有優化,於是有猿想:「資料中也有唯讀的和可修改的,我是不是可以把資料也分下段?這樣,我寫程式時如果手誤修改了唯讀的內容,那麼程式會報告錯誤。這該是多麼美妙的事情啊。」於是就有了那個經典的程式:

#include

int main(int argc ,char* argv)

好多懂一點的c

語言新手可能會以為輸出結果是 

"aello" 

,但是實際結果是:

linux: 

core dump 

吐核,我一直覺得吐核這個翻譯是個很

cute

的翻譯。其實也是段錯誤,陷入核心。終止程式,把控制權交給核心。應該是個

cpu異常中斷或者錯誤中斷。

windows 下面好像是段錯誤。既對唯讀段進行賦值操作的段錯誤。

所以,正確的寫法是:

#include

int main(int argc ,char* argv)

應該用字串陣列。字串編譯完了以後是放在資料段的唯讀區域的。而字串陣列是放在程式上下文中。既**段。(有同學可能會問,這不是資料麼。怎麼去了**段?額。這個我也很有點迷茫。)但是執行到printf("$s",str);

的時候,是進行了

n次壓棧操作,一方面儲存當前暫存器(程式上下文)以及其他要儲存的資訊,另一方面把引數(

str指標)壓棧,傳遞給

printf

函式。這個過程有個準業術語,叫呼叫慣例(引數傳遞方式-

#include

char *str = "hello";

*str = 'a';

int main(int argc ,char* argv)

則"hello" 

和 str 

是放在資料段裡了。

剛才說到分段,無到有是馮大神指出的光明道路,那麼有到多則是無數程式猿的智慧型總結。

自從有程式猿發現可以根據實際需求來再次分段之後,各種分段就如雨後春筍般破土而出。

比如注釋資訊段,除錯段,編譯器資訊段,目標平台段,動態連線資訊段,符號雜湊表段,等等。linux 

下面有檢視可執行檔案(以及

so動態

/binutils

。windows

馬克思告訴我們,事物總是不斷發展的。各種段之後,慢慢的記憶體大了,有了記憶體管理的概念,分段的好處是記憶體可以充分利用。比如1m

的記憶體劃分為

512個段,記憶體利用更充分了。再後來慢慢的有了

os的概念,於是又有了堆(

heap

)段的概念。又有了分頁方式的記憶體管理,每次分配給程式的記憶體顆粒更小了,所以記憶體利用率得到了極大的提高。既:生產力氣得到了極大的發展。

乙個小故事

從前有乙個叫馬里的小女孩,她四歲的時候,天使飛到她家來看她,天使問她,馬里,你長大了,有什麼願望要我幫你實現嗎?馬里說,我想在我20歲生日的時候找到乙個男朋友,他叫汗斯,他要有長長的頭髮,他要會彈結他,會唱歌,我們會生4個孩子,都是女孩,她們都要去學校學跳芭蕾。馬里長大了,在她17歲的時候,她真的遇...

乙個關於博士的小故事

下面是我看到的乙個小故事,也許已經很舊了,但是很有教育意義,覺得不錯就發上來了。有乙個博士分到一家研究所,成為學歷最高的乙個人。有一天他到單位後面的小池塘去釣魚,正好正副所長在他的一左一右,也在釣魚。他只是微微點了點頭,這兩個本科生,有啥好聊的呢?不一會兒,正所長放下釣竿,伸伸懶腰,蹭蹭蹭從水面上如...

有關敬業的乙個小故事

新年伊始,筆者所在公司天濟大藥房副總就輪流著在各門店培訓員工,今天輪到我們後勤人員,講的是關於如何忠誠服務於企業 如何敬業的問題,以及現代職場新思維,當中插入了乙個故事讓筆者感觸頗深,想把大概的故事情節給記錄下來,加深影響。有兩個年輕人,咱們就暫且用a君 和 b 君替代吧。他們都是從同一家學校同一類...