編程語言的巔峰境界
“哇塞,怎么可能這么簡單!”
當(dāng)C語言老頭兒還是小伙子的時候,***次見到了匯編,發(fā)出了這么一聲感慨。
在C語言看來,這匯編的指令實在是太簡單了,簡單到了令人發(fā)指的地步,只有這么幾類指令:
數(shù)據(jù)傳輸類:
- 就是把數(shù)據(jù)從一個位置復(fù)制到另外一個位置,比如從內(nèi)存到寄存器,或者從寄存器到內(nèi)存, 或者從寄存器到寄存器。
算術(shù)和邏輯運算類:
- 無非就是加減乘除,AND, OR, 左移,右移
控制類:
- 比較兩個值,跳轉(zhuǎn)到某一個位置。
匯編老頭兒非常地驕傲, 他經(jīng)常囂張地說:“別看我的指令這么簡單,但是配合我的寄存器和內(nèi)存, 卻能完成你們這些所謂的高級語言的所有功能!”
這寄存器是什么鬼? C語言腦海中只有內(nèi)存和指針,根本就沒有什么寄存器的概念, 實際上,這是屬于CPU阿甘的,容量有限,但是速度超級快的存儲部件。
圖片來源: http://pascalturbo.net/cpu-registers/
數(shù)組
C看著匯編這單薄的小身板,想到自己那優(yōu)雅的if , 漂亮的while, for ,還有那極為重要的函數(shù)調(diào)用,心里不由得泛起嘀咕:我的程序怎么可能被編譯成這么簡單的匯編?
雖然心里有點瞧不上,但C小伙還是挺恭敬的:“前輩,在我這里有個數(shù)組的概念,編譯成匯編是什么樣?”
- int num[10];
- num[0] = 100;
- num[1] = 200;
除了機器語言,那就屬匯編最老,連C語言的***個編譯器都是用匯編寫的,當(dāng)之無愧的前輩。
匯編老頭兒沒想到C小伙兒連這個問題都沒弄清楚,說道:“我這里只認(rèn)寄存器和內(nèi)存,你這所謂數(shù)組,就是內(nèi)存的一段連續(xù)的空間嘛,我只要知道開始地址就可以了。”
C小伙兒一看,好家伙,連變量名num都不要了。 不過說得也是, 匯編老頭只要記住初始地址,順著地址就能找到所有東西。
“咦,這個什么0x000083d0不就相當(dāng)于我的指針么? ”
“是啊,不過在我這里,都是地址,忘掉指針吧!”
條件分支
C小伙又想到了自己的if else,在匯編中該怎么處理?
- if(x < y ){
- return y - x;
- } else {
- return x -y ;
- }
匯編老頭兒說:“你們這些高級語言啊,就愛搞復(fù)雜化,怎么不用goto呢?”
C小伙說:“goto 被迪杰斯特拉認(rèn)為是有害的,會破壞結(jié)構(gòu)化,不建議使用!”
“唉,簡單就是美,你們這些高級語言懂不了,我這里很簡單,就是比較和跳轉(zhuǎn)指令,從一個地方跳到另外一個地方執(zhí)行就行了。”
匯編老頭兒一遍感慨,一遍寫道:
我們假設(shè)
- %eax 寄存器保存的是y的值,
- %edx 寄存器保存的是x的值。
- cmpl %eax, %edx ; 比較x和y
- jge .L1 ; 如果x >= y,跳轉(zhuǎn)到.L1處去執(zhí)行
- subl %edx,%eax ; 計算y-x,結(jié)果存到eax寄存器中
- jmp .done ; 跳轉(zhuǎn)到.done標(biāo)簽處
- .L1:
- subl %eax, %edx ; 計算 x-y
- movl %edx, %eax ; 把結(jié)果存到eax寄存器中
- .done: ; 計算結(jié)束,結(jié)果保存在eax寄存器中
(碼農(nóng)翻身注:這個例子來源于《深入理解計算機系統(tǒng)》)
C小伙兒看了半天,終于搞明白了這段匯編程序的含義,這所謂的jge也就是做一個判斷,然后跳轉(zhuǎn)到特定位置去執(zhí)行,就像是if 和 goto 的結(jié)合。
匯編老頭兒看到C小伙兒懂了, 問道:“你想想你的while 循環(huán),for 循環(huán),是不是if 和 goto 的包裝而已?”
C小伙兒想了一會:“確實是這樣!”
“這不就結(jié)了,我的匯編看起來簡單,但是卻能表達(dá)你所有的流程控制語句,不管什么if else, while, for ,switch ,對吧?”
C小伙兒覺得匯編老頭兒說的都是歪理:“這goto是簡單,可是程序讀起來就非常復(fù)雜了啊!”
匯編老頭兒說:“你算是說道了點子上,所謂高級語言,主要是方便人類的編寫和閱讀的,是為了提升人類的效率。 在我這里,主要是讓CPU阿甘執(zhí)行的,那傻小子,速度飛快,什么也不懂,你只要告訴它指令就行,越簡單越好。”
沒想到CPU阿甘聽到了對它的嘲諷,不滿地說:“老伙計,又在背后說我的壞話,我執(zhí)行了億萬條指令以后,早就悟出了程序的局部性原理,這你懂不懂?”
(碼農(nóng)翻身注:詳情參見《CPU阿甘》)
函數(shù)調(diào)用
C小伙看到不能難倒匯編老頭兒,想到了自己可以定義函數(shù),精神一振,問道:“函數(shù)調(diào)用你怎么處理啊?”
- int funcA(int a){
- ......
- funcB(10)
- ......
- }
- int funcB(int b){
- ......
- funcC();
- ......
- }
看看,這funcA調(diào)用funcB, funcB又調(diào)用funcC,函數(shù)嵌套調(diào)用,你那簡單的指令能處理? C小伙兒心里暗想。
匯編老頭兒不慌不忙:“你可算是問了一個有價值的問題,不過這也難不倒我,我需要內(nèi)存配合一下就行了。”
“看到里邊的棧幀沒有,每個棧幀都表示一個函數(shù)的調(diào)用!”
“那這棧幀中有什么東西?” C小伙兒問道。
“細(xì)節(jié)太復(fù)雜,給你畫個示意圖看看吧!”
“不對啊,你這棧幀中有輸入?yún)?shù),有返回值,可是沒有函數(shù)代碼啊?代碼去哪兒了?”
“真是幼稚! 這是運行時在內(nèi)存中對函數(shù)的表達(dá),那代碼肯定是在代碼段啊。” 匯編老頭兒嘲諷道。
代碼段的指令不斷被CPU阿甘執(zhí)行,遇到函數(shù)調(diào)用,就建立新的棧幀,函數(shù)調(diào)用結(jié)束,棧幀就會銷毀,廢棄。然后返回上一個棧幀。
C小伙兒意識到自己犯了一個大錯誤,他老是想著代碼的靜態(tài)結(jié)構(gòu),而忽略了運行時的表示。
編程語言的***
他急于挽回面子,趕緊給C++打電話求援:“兄弟,快過來,治一下這個匯編老頭兒!”
C++了解了事情的經(jīng)過,說道:“兄弟,不行啊,別看我有class, 但是最終我也得變成過程化的程序,翻譯成匯編,和你是一樣一樣的。”
(碼農(nóng)翻身注: 參見《面向?qū)ο笫ソ?jīng)》)
“那Python呢, Java 呢?” C小伙兒有點氣急敗壞。
“他們更不行了,是虛擬機中的語言,他們連匯編老頭兒的面兒都見不著,再說那虛擬機也是用你老兄C語言寫的啊!”
C小伙呆住了,可不是,自己是很多系統(tǒng)級軟件和編程語言的基礎(chǔ),已經(jīng)非常貼近硬件了,自己治不了匯編老頭兒,別人肯定也不行啊。
C小伙兒又想到了應(yīng)用層那復(fù)雜的業(yè)務(wù)邏輯,他們都是由Python,Java, JavaScript等高級語言編寫的,還用到了什么OOD,設(shè)計模式,函數(shù)式,響應(yīng)式編程...... 但是它們都是一層層的抽象,幫助程序員更好地編寫程序,在***層,還是匯編啊。
他嘆了一口氣,對匯編老頭說:“前輩,我服了,您可真是編程語言的***啊。”
“不敢當(dāng),還有一個語言比我更厲害!”
“是誰?”
“機器語言! 只有0和1!不信你看看這程序員專屬的鍵盤。”