本系列拖了蠻久了,主要是因?yàn)長(zhǎng)Z寫的時(shí)候其實(shí)剛看到第二章,因此這一段時(shí)間快速看了下第三章,并花了點(diǎn)時(shí)間沉淀了一下,這才耽誤了下來。
本文是3.X系列的第一篇,也是匯編世界的開篇。LZ一直在想如何能讓這一系列稍微變得有趣一些,因?yàn)榈诙聦?shí)在是太枯燥了,連LZ都覺得無聊至極,不過LZ竟然鬼使神差的把課后題做了不少。匯編這一部分相對(duì)而言會(huì)好很多,盡管它依然不是我們熟悉的編程語(yǔ)言,但是終歸還是語(yǔ)言,而不再是我們幾乎不打交道的0和1。
為何要學(xué)習(xí)匯編語(yǔ)言
對(duì)于大部分猿友來說,平時(shí)寫的都是一些高級(jí)程序設(shè)計(jì)語(yǔ)言,是計(jì)算機(jī)領(lǐng)域的諸多大神,經(jīng)過幾層的封裝才讓我們享有了這樣的待遇。這樣一來,我們?cè)谄綍r(shí)的開發(fā)過程中,可以省去很多底層的麻煩。試想一下,倘若在你寫一個(gè)方法的時(shí)候,你還需要去操心哪些變量需要放在寄存器,哪些變量放在主存,放在寄存器的話又該放在哪一個(gè)里面,放在主存的話又該放在那個(gè)內(nèi)存區(qū)域等等這一類底層的問題,以及還要去記各種各樣的寄存器名稱和它們的作用等等諸如此類的事,你是否會(huì)崩潰呢。
因此這不難看出,高級(jí)語(yǔ)言給我們帶來了很多便捷,但是事情總不是十全十美的,這樣所帶來的便捷也同時(shí)引來了一些問題。這是因?yàn)槲覀兛吹降拇a,在實(shí)際執(zhí)行它們的時(shí)候,可能已經(jīng)面目全非了,所以很多時(shí)候會(huì)造成一些莫名其妙的問題發(fā)生。
舉一個(gè)小例子,LZ曾經(jīng)在群里問過類似的問題,這次LZ寫一個(gè)小程序,各位學(xué)過Java的猿友來看看這個(gè)程序的結(jié)果。
public class Main
{
public static void main(String[] args)
{
Integer a = 127;
Integer b = 127;
Integer c = 128;
Integer d = 128;
System.out.println(a == b);
System.out.println(c == d);
}
}
相信有不少人看不出來這個(gè)程序的問題在哪,覺得應(yīng)該輸出兩個(gè)true就對(duì)了。可是這個(gè)程序的結(jié)果卻是一個(gè)true和一個(gè)false,如果哪位猿友不信的話可以自己試一下。至于原因是什么,各位有興趣的可以去研究下Java的自動(dòng)拆裝箱,另外再看一下Integer對(duì)象的valueOf方法緩存的范圍,答案就會(huì)自動(dòng)揭曉。
產(chǎn)生這個(gè)問題的根本原因,其實(shí)還是因?yàn)榫幾g器給開發(fā)者蒙上了一層迷霧,導(dǎo)致一些開發(fā)人員只知其然,而不知其所以然,他們根本不清楚自己寫出來的程序,實(shí)際上到底是如何運(yùn)行的。這樣的一層迷霧注定會(huì)降低開發(fā)者的水平,所以為了提高自己,我們有必要揭開這層迷霧。對(duì)于C/C++的開發(fā)者來講,揭開這層迷霧其實(shí)就是了解匯編語(yǔ)言的過程。
匯編語(yǔ)言對(duì)于C/C++程序猿來講,就像class文件對(duì)于Java程序猿是一樣的,因?yàn)樗鼈兌际蔷幾g器處理后的產(chǎn)物,其實(shí)LZ說了這么多,只是想說一件事,那就是了解匯編語(yǔ)言的知識(shí),對(duì)我們平時(shí)的開發(fā)有著不可忽視的好處,尤其是對(duì)于從事C/C++的開發(fā)者來說,好處更是無窮無盡的。
可能會(huì)有猿友覺得,LZ是一個(gè)靠Java吃飯的家伙,了解匯編語(yǔ)言是不是有點(diǎn)多此一舉了,畢竟Java語(yǔ)言離匯編還是有點(diǎn)太遠(yuǎn)了吧。畢竟Java要先編譯成class文件,然后交給虛擬機(jī)的執(zhí)行引擎,而虛擬機(jī)的執(zhí)行引擎則是由C/C++來實(shí)現(xiàn)的,C/C++又需要經(jīng)過預(yù)處理和GCC編譯器的編譯才能最終成為匯編語(yǔ)言。這猛地一看,Java確實(shí)離匯編語(yǔ)言太遠(yuǎn)了。
可是LZ想說的是,無論你處于什么樣的一個(gè)崗位當(dāng)中,只要你做的事是指揮計(jì)算機(jī)幫助你完成一些事情,那么你就必須了解計(jì)算機(jī)如何幫你完成這些事情,否則你就只會(huì)指揮,而不會(huì)懂得如何去做。不知道如何去做的后果就是,你不會(huì)知道如何才能做的更好,反映到現(xiàn)實(shí)當(dāng)中,就是你不知道如何寫出更好的程序。這點(diǎn)其實(shí)不難理解,試想一下,你都不知道你的程序?qū)嶋H上是如何運(yùn)行的,你又怎么可能知道怎么寫是更好的呢。
初次體會(huì)匯編
在編譯一段C語(yǔ)言程序的過程中,其實(shí)做了很多步驟,比如預(yù)編譯處理、編譯處理、匯編處理以及鏈接處理。我們要了解的匯編語(yǔ)言,就是在編譯處理后的產(chǎn)物。因此我們可以在GCC的編譯器當(dāng)中加入一些參數(shù)來控制它只生成匯編語(yǔ)言,而不進(jìn)行匯編處理和鏈接處理。
我們看下面這一段簡(jiǎn)單的C語(yǔ)言代碼,假設(shè)為sum.c。
int simple(int *xp,int y){
int t = *xp+y;
*xp=t;
return t;
}
我們使用GCC編譯器加-S參數(shù)來編譯這段代碼,最終我們可以得到一個(gè)sum.s的文件,我們使用cat來查看一下這個(gè)文件。
.file "sum.c"
.text
.globl simple
.type simple, @function
simple:
pushl %ebp
movl %esp, %ebp
subl $16, %esp
movl 8(%ebp), %eax//這一步是從主存取變量xp
movl (%eax), %eax//取*xp的值
addl 12(%ebp), %eax//計(jì)算*xp+y,并存到%eax寄存器
movl %eax, -4(%ebp)//將*xp+y賦給變量t
movl 8(%ebp), %eax//再取xp
movl -4(%ebp), %edx//取t
movl %edx, (%eax)//執(zhí)行t->*xp
movl -4(%ebp), %eax//將t放入%eax準(zhǔn)備返回
leave
ret
.size simple, .-simple
.ident "GCC: (Ubuntu 4.4.3-4ubuntu5.1) 4.4.3"
.section .note.GNU-stack,"",@progbits
這里我們主要看一下匯編語(yǔ)言是如何描述我們的計(jì)算過程的,因此LZ只是簡(jiǎn)單的加了幾個(gè)注釋,來大致描述下上面的程序的計(jì)算過程。其中需要說明的是,以%開頭的為寄存器,有小括號(hào)的為主存。
熟悉GCC的猿友們應(yīng)該知道,我們可以控制編譯器的優(yōu)化級(jí)別,因此我們使用另外一種方式來編譯一下sum.c,我們?cè)?S的基礎(chǔ)上再加一個(gè)-O1的參數(shù)。之后使用cat打開sum.s文件。
.file "sum.c"
.text
.globl simple
.type simple, @function
simple:
pushl %ebp
movl %esp, %ebp
movl 8(%ebp), %edx//取xp
movl 12(%ebp), %eax//取y
addl (%edx), %eax//計(jì)算*xp+y并存到%eax寄存器,準(zhǔn)備返回
movl %eax, (%edx)//將*xp+y存入*xp
popl %ebp
ret
.size simple, .-simple
.ident "GCC: (Ubuntu 4.4.3-4ubuntu5.1) 4.4.3"
.section .note.GNU-stack,"",@progbits
可以很明顯的看出,匯編指令的數(shù)目急劇減少,這里L(fēng)Z也加入了簡(jiǎn)單的注釋。從LZ簡(jiǎn)單的注釋中可以看出,這里的優(yōu)化主要是去掉了變量t的存在,因此減少了指令數(shù)。
如果哪位猿友實(shí)在看不明白這兩段匯編語(yǔ)言的含義,可以暫且忽略,這里L(fēng)Z只是讓各位體驗(yàn)一下匯編語(yǔ)言的格式,以及親自接觸一下匯編語(yǔ)言,我們的目的并不是搞清楚它的意義。相信經(jīng)過3.X的系列講解,各位猿友再回來看這兩段匯編代碼時(shí),應(yīng)該會(huì)很輕松的看出其中的意義。
文章小結(jié)
這一章拖的時(shí)間有點(diǎn)久,主要是因?yàn)長(zhǎng)Z作為一個(gè)Java開發(fā)人員來講,對(duì)匯編語(yǔ)言的學(xué)習(xí)有些許難度,畢竟LZ并不擅長(zhǎng)C/C++。還有一個(gè)原因,則是由于LZ希望盡量的搞清楚來龍去脈,以免誤導(dǎo)某些猿友。
當(dāng)然了,就算如此,LZ也不敢保證現(xiàn)在對(duì)3.X的內(nèi)容已經(jīng)了如指掌,因此如果文中有任何與各位猿友的理解不一致的地方,希望各位猿友盡管提出。不僅可以避免誤導(dǎo)看博文的猿友,還可以幫助LZ糾正錯(cuò)誤的認(rèn)識(shí)。
好了,本文的主要目的就是將各位猿友拉近匯編的世界,因此就只是簡(jiǎn)單的介紹了一下。接下來,我們將深入的討論寄存器、數(shù)據(jù)格式以及一些匯編指令。