上一章我們已經著重討論了數據傳送(或者說復制)指令,相信各位猿友現在都已經對此有一些了解了。說真的,LZ在看第三章的過程中,不斷的被匯編的魅力深深的震撼,這些看似簡單的匯編指令,卻可以將復雜的程序井然有序的執行完畢,實在是讓人驚歎。時至今日,這本看似枯燥無比卻實則魅力十足的書,已經深深的將LZ吸引了。
希望各位猿友也有這樣的感覺,這是一種非常好的感覺,接下來,各位就一起和LZ來認識認識新的指令吧。
算術與邏輯運算包括很多種,估計各位猿友也能很快的想出來,比如最常見的加減乘除、與或非、左移右移等等。這裡可能還有一個各位猿友不太容易想到的,就是取地址運算符,不過這個運算指令卻是LZ看過這一部分之後,覺得最精妙的一個指令。
接下來LZ將書中的一個表格貼上來,各位猿友可以先大致浏覽下裡面的指令。
查看本欄目
可以看到,此時在存儲器中,地址為5x+4的區域的值為1000。那麼此時若是進行movl 4(%edx,%edx,4),%eax操作,很顯然,%eax的值應該為1000,也就是下圖。
但是如果進行leal 4(%edx,%edx,4),%eax操作的話,%eax的值就不是1000了,因為leal指令不會去取存儲器當中的值,因此寄存器%eax的值應該是5x+4。
試想一下,倘若在地址為5x+4的位置存儲的是變量i,那麼其實這條指令就相當於&i操作,這也就是C語言當中的&取地址操作的匯編級做法。各位猿友感覺如何,是否很神奇呢。
由於其它的指令都相對比較簡單,因此LZ這裡就不一一介紹了,這裡我們用一個小程序來做一個示例,順便也去看一下上面的算術與邏輯運算指令都是被如何使用的。我們就考慮書上的一個小例子,其中的C程序代碼如下。
int arith(int x, int y , int z){ int t1 = x+y; int t2 = z*48; int t3 = t1&0xFFFF; int t4 = t2*t3; return t4; }
這裡面包含了加、乘、與運算,我們使用-O1和-S參數編譯sum.c這個文件,使用cat sum.s查看它,會得到如下的匯編代碼。
.file "sum.c" .text .globl arith .type arith, @function arith: pushl %ebp movl %esp, %ebp //以上為棧幀建立 movl 16(%ebp), %eax leal (%eax,%eax,2), %edx sall $4, %edx movl 12(%ebp), %eax addl 8(%ebp), %eax andl $65535, %eax imull %edx, %eax //以下為棧幀完成 popl %ebp ret .size arith, .-arith .ident "GCC: (Ubuntu 4.4.3-4ubuntu5.1) 4.4.3" .section .note.GNU-stack,"",@progbits
這裡面還有leal指令,可以看到程序當中並沒有取地址&操作,所以這裡的leal指令不是用來取地址的,LZ使用圖示來給各位演示這個程序的運行過程。首先便是棧幀的建立過程,棧幀建立好以後,寄存器和存儲器的狀態如下所示。
以上便是建立好的棧幀,同上一次一樣,幀指針和棧指針都指向一個新的位置,在幀指針偏移量為8、12、16的地方存儲著傳遞進來的參數x、y、z。接下來我們就開始分析,在匯編代碼層次,是如何完成上述C語言程序當中的一系列動作的。
首先是一個mov指令,它的作用很簡單,就是將參數z取入寄存器,下面是它的匯編代碼以及圖示。
movl 16(%ebp), %eax
上面的指令比較簡單,接下來的這條指令就比較特別了,是一條leal指令。這裡的leal指令不是用來取地址的,而是用來進行乘法運算的,它的目的是將%eax寄存器當中的值乘以3,然後發送至%edx寄存器。而采用的方式則是2*x + x的方式,這正是我們之前講過的乘法優化算法,使用移位和加法來計算乘法。接下來看看它的指令與圖示。
leal (%eax,%eax,2), %edx
上面計算3z的目的,在接下來這一條指令就看出來了。接下來的一條指令是sal左移操作,位數為4,左移4位其實就相當於乘以16,因此接下來的一條指令其實就相當於將寄存器%edx當中的值乘以16,這其實剛好是在計算48*z。從這裡也可以看出來,在執行C程序的時候,並不一定會按照程序當中的順序去計算。以下是sal指令的內容與圖示。
sall $4, %edx
接下來的指令依然是簡單的取參數y,因此LZ這裡就不再多解釋了,直接上內容和圖示。
movl 12(%ebp), %eax
下面的一條指令是add加法指令,它是將左邊操作數的值加到右邊的目的操作數。也就是將內存地址為8(%ebp)的值加到%eax寄存器,而8(%ebp)這個位置存的剛好是x,因此這裡計算的便是x+y的值,而結果會存入%eax寄存器。以下是指令的內容和圖示。
addl 8(%ebp), %eax
接下來是一條與運算指令and,它計算的則是t1與0xFFFF(十進制就是65535)的與運算,t1的值為x+y,此時就存在%eax寄存器。我們來看下這條指令的內容與圖示。
andl $65535, %eax
接下來是最後一個計算過程的指令imul乘法指令,它的作用也是將左邊操作數的值乘到右邊的目的操作數上。也就是將%edx寄存器的值乘到%eax寄存器上面去,而%edx此時的值為48*z(也就是t2),而%eax的值為(x+y)&0xFFFF(也就是t3),兩者相乘則得到t4的值,結果將存在%eax寄存器,並且作為返回值返回。以下為內容與圖示。
imull %edx, %eax
查看本欄目