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