Linux下的匯編語言之GCC 內(nèi)聯(lián)匯編
作為最基本的編程語言之一,匯編語言雖然應(yīng)用的范圍不算很廣,但重要性卻勿庸置疑,因?yàn)樗軌蛲瓿稍S多其它語言所無法完成的功能。就拿 Linux 內(nèi)核來講,雖然絕大部分代碼是用 C 語言編寫的,但仍然不可避免地在某些關(guān)鍵地方使用了匯編代碼,其中主要是在 Linux 的啟動部分。由于這部分代碼與硬件的關(guān)系非常密切,即使是 C 語言也會有些力不從心,而匯編語言則能夠很好揚(yáng)長避短,最大限度地發(fā)揮硬件的性能。
GCC 內(nèi)聯(lián)匯編
用匯編編寫的程序雖然運(yùn)行速度快,但開發(fā)速度非常慢,效率也很低。如果只是想對關(guān)鍵代碼段進(jìn)行優(yōu)化,或許更好的辦法是將匯編指令嵌入到 C 語言程序中,從而充分利用高級語言和匯編語言各自的特點(diǎn)。但一般來講,在 C 代碼中嵌入?yún)R編語句要比"純粹"的匯編語言代碼復(fù)雜得多,因?yàn)樾枰鉀Q如何分配寄存器,以及如何與C代碼中的變量相結(jié)合等問題。
GCC 提供了很好的內(nèi)聯(lián)匯編支持,最基本的格式是:
- __asm__("asm statements");
例如:
- __asm__("nop");
如果需要同時執(zhí)行多條匯編語句,則應(yīng)該用"\\n\\t"將各個語句分隔開,例如:
- __asm__( "pushl %%eax \\n\\t"
- "movl $0, %%eax \\n\\t"
- "popl %eax");
通常嵌入到 C 代碼中的匯編語句很難做到與其它部分沒有任何關(guān)系,因此更多時候需要用到完整的內(nèi)聯(lián)匯編格式:
- __asm__("asm statements" : outputs : inputs : registers-modified);
插入到 C 代碼中的匯編語句是以":"分隔的四個部分,其中第一部分就是匯編代碼本身,通常稱為指令部,其格式和在匯編語言中使用的格式基本相同。指令部分是必須的,而其它部分則可以根據(jù)實(shí)際情況而省略。
在將匯編語句嵌入到C代碼中時,操作數(shù)如何與C代碼中的變量相結(jié)合是個很大的問題。GCC采用如下方法來解決這個問題:程序員提供具體的指令,而對寄存器的使用則只需給出"樣板"和約束條件就可以了,具體如何將寄存器與變量結(jié)合起來完全由GCC和GAS來負(fù)責(zé)。
在GCC內(nèi)聯(lián)匯編語句的指令部中,加上前綴'%'的數(shù)字(如%0,%1)表示的就是需要使用寄存器的"樣板"操作數(shù)。指令部中使用了幾個樣板操作數(shù),就表明有幾個變量需要與寄存器相結(jié)合,這樣GCC和GAS在編譯和匯編時會根據(jù)后面給定的約束條件進(jìn)行恰當(dāng)?shù)奶幚?。由于樣板操作?shù)也使用'%'作為前綴,因此在涉及到具體的寄存器時,寄存器名前面應(yīng)該加上兩個'%',以免產(chǎn)生混淆。
緊跟在指令部后面的是輸出部,是規(guī)定輸出變量如何與樣板操作數(shù)進(jìn)行結(jié)合的條件,每個條件稱為一個"約束",必要時可以包含多個約束,相互之間用逗號分隔開就可以了。每個輸出約束都以'='號開始,然后緊跟一個對操作數(shù)類型進(jìn)行說明的字后,最后是如何與變量相結(jié)合的約束。凡是與輸出部中說明的操作數(shù)相結(jié)合的寄存器或操作數(shù)本身,在執(zhí)行完嵌入的匯編代碼后均不保留執(zhí)行之前的內(nèi)容,這是GCC在調(diào)度寄存器時所使用的依據(jù)。
輸出部后面是輸入部,輸入約束的格式和輸出約束相似,但不帶'='號。如果一個輸入約束要求使用寄存器,則GCC在預(yù)處理時就會為之分配一個寄存器,并插入必要的指令將操作數(shù)裝入該寄存器。與輸入部中說明的操作數(shù)結(jié)合的寄存器或操作數(shù)本身,在執(zhí)行完嵌入的匯編代碼后也不保留執(zhí)行之前的內(nèi)容。
有時在進(jìn)行某些操作時,除了要用到進(jìn)行數(shù)據(jù)輸入和輸出的寄存器外,還要使用多個寄存器來保存中間計算結(jié)果,這樣就難免會破壞原有寄存器的內(nèi)容。在GCC內(nèi)聯(lián)匯編格式中的最后一個部分中,可以對將產(chǎn)生副作用的寄存器進(jìn)行說明,以便GCC能夠采用相應(yīng)的措施。
下面是一個內(nèi)聯(lián)匯編的簡單例子:
例4.內(nèi)聯(lián)匯編
- /* inline.c */
- int main()
- {
- int a = 10, b = 0;
- __asm__ __volatile__("movl %1, %%eax;\\n\\r"
- "movl %%eax, %0;"
- :"=r"(b) /* 輸出 */
- :"r"(a) /* 輸入 */
- :"%eax"); /* 不受影響的寄存器 */
- printf("Result: %d, %d\\n", a, b);
- }
上面的程序完成將變量a的值賦予變量b,有幾點(diǎn)需要說明:
變量b是輸出操作數(shù),通過%0來引用,而變量a是輸入操作數(shù),通過%1來引用。
輸入操作數(shù)和輸出操作數(shù)都使用r進(jìn)行約束,表示將變量a和變量b存儲在寄存器中。輸入約束和輸出約束的不同點(diǎn)在于輸出約束多一個約束修飾符'='。
在內(nèi)聯(lián)匯編語句中使用寄存器eax時,寄存器名前應(yīng)該加兩個'%',即%%eax。內(nèi)聯(lián)匯編中使用%0、%1等來標(biāo)識變量,任何只帶一個'%'的標(biāo)識符都看成是操作數(shù),而不是寄存器。
內(nèi)聯(lián)匯編語句的最后一個部分告訴GCC它將改變寄存器eax中的值,GCC在處理時不應(yīng)使用該寄存器來存儲任何其它的值。
由于變量b被指定成輸出操作數(shù),當(dāng)內(nèi)聯(lián)匯編語句執(zhí)行完畢后,它所保存的值將被更新。
在內(nèi)聯(lián)匯編中用到的操作數(shù)從輸出部的第一個約束開始編號,序號從0開始,每個約束記數(shù)一次,指令部要引用這些操作數(shù)時,只需在序號前加上'%'作為前綴就可以了。需要注意的是,內(nèi)聯(lián)匯編語句的指令部在引用一個操作數(shù)時總是將其作為32位的長字使用,但實(shí)際情況可能需要的是字或字節(jié),因此應(yīng)該在約束中指明正確的限定符:
限定符 | 意義 |
"m"、"v"、"o" | 內(nèi)存單元 |
"r" | 任何寄存器 |
"q" | 寄存器eax、ebx、ecx、edx之一 |
"i"、"h" | 直接操作數(shù) |
"E"和"F" | 浮點(diǎn)數(shù) |
"g" | 任意 |
"a"、"b"、"c"、"d" | 分別表示寄存器eax、ebx、ecx和edx |
"S"和"D" | 寄存器esi、edi |
"I" |
【編輯推薦】
- 2.3 用匯編語言編寫程序
- 術(shù)語匯編 基本CSS濾鏡概述
- 琢石成器—Windows環(huán)境下32位匯編語言程序設(shè)計
- 3.1.3 as86匯編語言程序的編譯和鏈接
- 3.1.2 as86匯編語言程序
- 3.1.1 as86匯編語言語法
- 3.2.6 as匯編命令