通過閱讀匯編代碼,我們能夠理解編譯器的優(yōu)化能力狸涌,并分析出代碼中潛在的低效率。
一最岗、機(jī)器級代碼
在整個編譯過程中帕胆,編譯器會完成大部分工作,將把C提供的相對比較抽象的執(zhí)行模型表示的程序轉(zhuǎn)化為處理器執(zhí)行的非嘲愣桑基本的指令懒豹。與目標(biāo)代碼相比,匯編代碼是可讀性更好的文本格式表示驯用。能夠理解匯編代碼以及它是如何與原始C代碼相對應(yīng)脸秽,是理解計算機(jī)如何執(zhí)行程序的關(guān)鍵一步。
對C程序員屏蔽的處理器狀態(tài)可見的
- 程序計數(shù)器(%eip):表示將要執(zhí)行下一條指令在存儲器中的地址
- 整數(shù)寄存器文件包含8個被命名的位置蝴乔,分別存儲32位的值记餐。這些寄存器可以存儲地址(對應(yīng)于C的指針)或整數(shù)數(shù)據(jù)。有的寄存器用來記錄某些重要的程序狀態(tài)薇正,其他寄存器用來保存臨時數(shù)據(jù)剥扣。
- 條件寄存器保存最近執(zhí)行的算術(shù)指令狀態(tài),實現(xiàn)控制流中的條件變化铝穷,比如if或while
- 浮點寄存器文件包含8個位置钠怯,用來存放浮點數(shù)據(jù)
(1)代碼示例
假設(shè)我們寫了一個C代碼文件code.c,包含下面這樣的過程定義
int accum = 0;
int sum(int x, int y)
{
int t = x + y;
acum += t;
return t;
}
在命令行執(zhí)行”-S“選項,看到C編譯器產(chǎn)生的匯編代碼
unix> gcc -O2 -S code.c
編譯器產(chǎn)生一個匯編文件code.s曙聂,不做其他近一步工作
匯編代碼文件包含各種聲明
sum:
pushl %ebp
movl %esp,%ebp
mov1 12(%ebp),%eax
addl 8(%ebp),%eax
addl %eax,accum
mov1 %ebp,%esp
pop1 %ebp
ret
上面代碼中每個縮進(jìn)去的行都對應(yīng)于一條機(jī)器指令晦炊。比如pushl 指令表示應(yīng)該將寄存器%ebp的內(nèi)容壓入棧中。
(2)訪問信息
一個IA32中央處理單元(cpu)包含一組8位值的寄存器,這些寄存器用來存儲整數(shù)數(shù)據(jù)和指針断国。下圖顯示了8個寄存器贤姆,以%e開頭。在過程處理中稳衬,對前三個寄存器(%eax,%ecx和%edx)的保存和恢復(fù)慣例將不同于接下來的三個寄存器(%ebx,%edi,%esi)霞捡,最后兩個寄存器(%ebp和%esp)保存著指向棧中重要位置的指針,只有根據(jù)棧慣例的標(biāo)準(zhǔn)才能修改這兩個寄存器中的值
(a)操作指示符
大多數(shù)指令都有一個或多個操作數(shù)薄疚,指示出執(zhí)行一個操作中藥引用的源數(shù)據(jù)值碧信,以及放置結(jié)果的目的位置。源數(shù)據(jù)值可以以常數(shù)形式給出街夭,或是從寄存器或存儲器中讀出砰碴,結(jié)果可以存放在寄存器或存儲器中。
各種操作數(shù)可能被分為三種類型板丽。
-
立即數(shù)呈枉,也就是常數(shù)值。
-577或$0x1F
- 寄存器猖辫,某個寄存器中的內(nèi)容
- 存儲器引用,根據(jù)計算出來的地址(有效地址)訪問某個存儲器位置砚殿。
有多種尋址模式:允許不同形式的存儲器引用住册。立即數(shù)偏移,基址寄存器瓮具,變之或索引寄存器荧飞,伸縮因子(必需是1、2名党、4叹阔、8)。
(3)數(shù)據(jù)傳送指令
最頻繁的指令是執(zhí)行數(shù)據(jù)傳送指令传睹。操作符指令能夠完成許多機(jī)器中要好幾條指令才能完成的功能耳幢。下圖列出一些重要的數(shù)據(jù)傳送指令,最常用的是傳送雙字的movl指令欧啤。
源操作數(shù)指令一個值睛藻,它可以是立即數(shù),可以存放在寄存器中邢隧,也可以存放在存儲器中店印。目的操作數(shù)指定一個位置,它可以是寄存器倒慧,也可以是存儲器地址按摘。
第一個是原操作數(shù)包券,第二個是目的操作數(shù)。
movb指令是類似的炫贤,除了它只傳送一個字節(jié)溅固。movw傳送兩個字節(jié)。movsbl和movzbl指令負(fù)責(zé)拷貝一個字節(jié)兰珍,并設(shè)置目的操作數(shù)中其余的位侍郭。movsbl指令的源操作數(shù)時單字節(jié)的,它執(zhí)行符號擴(kuò)展到32位(將高24位設(shè)置為源字節(jié)的最高位)掠河,然后拷貝到雙字的目的中亮元。movzbl指令的源操作數(shù)時單字節(jié)的,在前面加24個0擴(kuò)展到32位口柳,并將結(jié)果拷貝到雙字的目的中。
pushl和popl指令都只有一個操作數(shù)----同于壓入的數(shù)據(jù)源和用于彈出的目的數(shù)據(jù)有滑。程序棧存放在儲存器中某個區(qū)域跃闹。%esp保存棧頂元素的地址
(4) 算術(shù)和邏輯操作
(a)加載有效地址
加載有效地址(leal)實際上是movl指令的變形,從存儲器讀數(shù)據(jù)到寄存器毛好,實際上根本沒有引用存儲器望艺。第一個操作看上去是一個寄存器引用,但該指令并不是從指定的位置讀入數(shù)據(jù)肌访,而是將有效地址寫入到目的操作數(shù)(如寄存器)找默。C中&S說明這種操作,為后面的存儲器引用產(chǎn)生指針吼驶。例子惩激,如果寄存器%eax值為x,指令leal 7(%edx,%eax ,4),%eax將設(shè)置寄存器%eax的值為x,那么leal 7(%edx,%edx,4),%eax將設(shè)置%eax的值為5x+7。注意目的操作數(shù)必須是寄存器蟹演。
(b)一元和二元操作
第二類操作是一元操作风钻,只有一個操作數(shù),即做源酒请,也作目的骡技。這個操作數(shù)可以是一個寄存器,也可以是一個存儲器位置羞反。比如說布朦,指令incl(%esp)會使棧頂元素加1。這種說法讓人想起C中的加1運算符(++)和減1運算符(--)
第三類是二元操作昼窗,第二個操作數(shù)既是源又是目的是趴。這種語法讓人想起C中像+=這樣的賦值運算符。注意澄惊,源操作數(shù)是第一個右遭,目的操作數(shù)時第二個做盅,這是不可交換操作持有的枚荣。例如指令subl %eax,%edx使寄存器%edx的值減去%eax中的值阅茶。第一個操作可以是立即數(shù),寄存器或存儲器位置畏吓。第二個操作數(shù)可以是寄存器或存儲器圍桌滚婉。不過movl指令一樣图筹,兩個操作數(shù)不能同時都是存儲器位置。
(c)位移操作
先給出位移量让腹,然后是待位移的值远剩,可以進(jìn)行算術(shù)或邏輯右移。移位量用單個字節(jié)編碼骇窍。位移量可以是一個立即數(shù)瓜晤,或者存放在單字節(jié)寄存器中%cl中。左移指令:sall,shll腹纳。兩者效果都一樣痢掠,都是將右邊填上0。右移指令sarl執(zhí)行算術(shù)移位(填上符號位
嘲恍,而shrl執(zhí)行邏輯位移(填上0)