C 语言内联汇编介绍
文章目錄
- 為什么要用內(nèi)聯(lián)匯編
- 內(nèi)聯(lián)匯編的基本要素
- 語法
- 匯編語句模板
- 操作數(shù)
- 輸出部分和輸入部分
- 操作數(shù)約束
- 常用約束
- 寄存器操作數(shù)約束
- 內(nèi)存操作數(shù)約束 (m)
- 匹配(數(shù)字)約束
為什么要用內(nèi)聯(lián)匯編
首先,對于那些頻繁調(diào)用的函數(shù),為了提高執(zhí)行效率,直接用匯編寫比較好。
其次,有些功能只能用匯編實現(xiàn),比如開中斷和關(guān)中斷:
#define sti() __asm__ ("sti"::) #define cli() __asm__ ("cli"::)內(nèi)聯(lián)匯編的重要性體現(xiàn)在它能夠靈活操作,而且可以使其輸出通過 C 變量顯示出來。因為它具有這種能力,所以 “asm” 可以用作匯編指令和包含它的 C 程序之間的接口
內(nèi)聯(lián)匯編的基本要素
{int a=10, b;asm ("movl %1, %%eax;movl %%eax, %0;":"=r"(b) /* output */ :"r"(a) /* input */:"%eax"); /* clobbered register */ }在上例中,我們使用匯編指令使 “b” 的值等于 “a”。請注意以下幾點:
- “b” 是輸出操作數(shù),由 %0 引用,“a” 是輸入操作數(shù),由 %1 引用。
- “r” 是操作數(shù)的約束,它指定將變量 “a” 和 “b” 存儲在寄存器中。請注意,輸出操作數(shù)約束應(yīng)該帶有一個約束修飾符 “=”,指定它是輸出操作數(shù)。
- 要在 “asm” 內(nèi)使用寄存器 %eax,%eax 的前面應(yīng)該再加一個 %,換句話說就是 %%eax,因為 “asm” 使用 %0、%1 等來標(biāo)識變量。任何帶有一個 % 的數(shù)都看作是輸入/輸出操作數(shù),而不認為是寄存器。
- 第三個冒號后的修飾寄存器 %eax 告訴將在 “asm” 中修改 GCC %eax 的值,這樣 GCC 就不使用該寄存器存儲任何其它的值。
- movl %1, %%eax 將 “a” 的值移到 %eax 中, movl %%eax, %0 將 %eax 的內(nèi)容移到 “b” 中。
- 因為 “b” 被指定成輸出操作數(shù),因此當(dāng) “asm” 的執(zhí)行完成后,它將反映出更新的值。換句話說,對 “asm” 內(nèi) “b” 所做的更改將在 “asm” 外反映出來。
語法
__asm__(匯編語句模板: 輸出部分: 輸入部分: 破壞描述部分)共四個部分:匯編語句模板,輸出部分,輸入部分,破壞描述部分。
各部分使用冒號格開,匯編語句模板必不可少,其他三部分可選。
如果使用了后面的部分,而前面部分為空,也需要用冒號格開,例如:
__asm__ __volatile__("cli": : :"memory")其中,“asm” 是內(nèi)聯(lián)匯編語句關(guān)鍵詞;
匯編語句模板:就是我們寫匯編指令的地方;
輸出部分:表示當(dāng)這段嵌入?yún)R編執(zhí)行完之后,哪些寄存器用于存放輸出數(shù)據(jù)。同時,這些寄存器會分別對應(yīng)一 C 語言表達式值或一個內(nèi)存地址;
輸入部分:表示在開始執(zhí)行匯編代碼時,這里指定的一些寄存器中應(yīng)存放的輸入值,它們也分別對應(yīng)著一 C 變量或常數(shù)值。
破壞描述部分:表示你已對其中列出的寄存器中的值進行了改動,GCC 編譯器不能再依賴于它原先對這些寄存器加載的值。如果必要的話,GCC 需要重新加載這些寄存器。因此我們需要把那些沒有在輸出或輸入寄存器部分列出,但是在匯編語句中明確使用到或隱含使用到的寄存器名列在這個部分中。
匯編語句模板
匯編程序模板是一組插入到 C 程序中的匯編指令(可以是單個指令,也可以是一組指令)。每條指令都應(yīng)該由雙引號括起,或者整組指令應(yīng)該由雙引號括起。每條指令還應(yīng)該用一個定界符結(jié)尾。有效的定界符為新行 (\n) 和分號。 ‘\n’ 后可以跟一個 tab(\t) 作為格式化符號,增加 GCC 在匯編文件中生成的指令的可讀性。 指令通過數(shù) %0、%1 等來引用 C 表達式(指定為操作數(shù))。
如果希望確保編譯器不會在 “asm” 內(nèi)部優(yōu)化指令,可以在 “asm” 后使用關(guān)鍵字 “volatile”。如果程序必須與 ANSI C 兼容,則應(yīng)該使用 __asm__和 __volatile__,而不是 asm 和 volatile。
操作數(shù)
C 表達式用作 “asm” 內(nèi)的匯編指令操作數(shù)。在匯編指令通過對 C 程序的 C 表達式進行操作來執(zhí)行有意義的作業(yè)的情況下,操作數(shù)是內(nèi)聯(lián)匯編的主要特性。
每個操作數(shù)都由操作數(shù)約束字符串指定,后面跟用括弧括起的 C 表達式,例如:“constraint” (C expression)。操作數(shù)約束的主要功能是確定操作數(shù)的尋址方式。
可以在輸入和輸出部分中同時使用多個操作數(shù)。每個操作數(shù)由逗號分隔開。
在匯編語句模板內(nèi)部,操作數(shù)由數(shù)字引用。如果總共有 n 個操作數(shù)(包括輸入和輸出),那么第一個輸出操作數(shù)的編號為 0,逐項遞增,最后那個輸入操作數(shù)的編號為 n -1。
舉例
static __inline__ void atomic_add(int i, atomic_t *v) {__asm__ __volatile__(LOCK "addl %1,%0":"=m" (v->counter):"ir" (i), "m" (v->counter)); }上面的例子,%0 就代表 v->counter,%1 就代表 i,%2 也代表 v->counter(代碼中沒有用 2%)
輸出部分和輸入部分
每個輸出操作數(shù)的限定字符串必須包含“=”,表示他是一個輸出操作數(shù)。
下面這個例子有助于理解輸入和輸出操作數(shù),也展示了寄存器約束 “r” 的用法。
int main(void) {int x = 10, y;asm ("movl %1, %%eax;"movl %%eax, %0;":"=r"(y) /* y is output operand */:"r"(x) /* x is input operand */:"%eax"); /* %eax is clobbered register */ }在該例中,x 的值復(fù)制為 “asm” 中的 y。x 和 y 都通過存儲在寄存器中傳遞給 “asm”。為該例生成的匯編代碼如下:
main: pushl %ebp movl %esp,%ebp subl $8,%esp movl $10,-4(%ebp) movl -4(%ebp),%edx /* x=10 is stored in %edx */ #APP /* asm starts here */ movl %edx, %eax /* x is moved to %eax */ movl %eax, %edx /* y is allocated in edx and updated */ #NO_APP /* asm ends here */ movl %edx,-8(%ebp) /* value of y in stack is updated with the value in %edx */當(dāng)使用 “r” 約束時,GCC 在這里可以自由分配任何寄存器。在我們的示例中,它選擇 %edx 來存儲 x。在讀取了 %edx 中 x 的值后,它為 y 也分配了相同的寄存器。
注意第 11 行,因為 y 是在輸出操作數(shù)部分中指定的,所以 %edx 中更新的值存儲在 -8(%ebp),堆棧上 y 的位置中。如果 y 是在輸入部分中指定的,那么即使它在 y 的臨時寄存器存儲值 (%edx) 中被更新,堆棧上 y 的值也不會更新。
因為 %eax 是在修飾列表中指定的,GCC 不在任何其它地方使用它來存儲數(shù)據(jù)。
在這個例子中,GCC 為輸入的 x 和作為輸出的 y 分配了同一個寄存器 %edx。
要確保輸入和輸出分配到不同的寄存器中,可以指定 & 約束修飾符。下面是添加了約束修飾符的示例。
int main(void) {int x = 10, y;asm ("movl %1, %%eax; "movl %%eax, %0;":"=&r"(y) /* y is output operand, note the & constraint modifier. */:"r"(x) /* x is input operand */:"%eax"); /* %eax is clobbered register */ }以下是為該示例生成的匯編代碼,從中可以明顯地看出 x 和 y 存儲在 “asm” 中不同的寄存器中。
main: pushl %ebp movl %esp,%ebp subl $8,%esp movl $10,-4(%ebp) movl -4(%ebp),%ecx /* x, the input is in %ecx */ #APPmovl %ecx, %eaxmovl %eax, %edx /* y, the output is in %edx */ #NO_APP movl %edx,-8(%ebp)為 x 分配了 %ecx,為 y 分配了 %edx
操作數(shù)約束
前面提到過,“asm” 中的每個操作數(shù)都應(yīng)該由操作數(shù)約束字符串描述,后面跟用括弧括起的 C 表達式。操作數(shù)約束主要是確定指令中操作數(shù)的尋址方式。約束也可以指定:
- 是否允許操作數(shù)位于寄存器中,以及它可以包括在哪些種類的寄存器中
- 操作數(shù)是否可以是內(nèi)存引用,以及在這種情況下使用哪些種類的地址
- 操作數(shù)是否可以是立即數(shù)
約束還要求兩個操作數(shù)匹配。
常用約束
在可用的操作數(shù)約束中,只有一小部分是常用的;下面列出了這些約束以及簡要描述。有關(guān)操作數(shù)約束的完整列表,請參考 GCC 和 GAS 手冊。
寄存器操作數(shù)約束
要指定寄存器,必須通過使用特定的寄存器約束直接指定寄存器名。
對應(yīng)關(guān)系是
a %eax b %ebx c %ecx d %edx S %esi D %edi內(nèi)存操作數(shù)約束 (m)
當(dāng)操作數(shù)位于內(nèi)存中時,任何對它們執(zhí)行的操作都將在內(nèi)存位置中直接發(fā)生,這與寄存器約束正好相反,后者先將值存儲在要修改的寄存器中,然后將它寫回內(nèi)存位置中。
當(dāng)需要在 “asm” 內(nèi)部更新 C 變量,而您又確實不希望使用寄存器來保存其值時,使用內(nèi)存約束最為有效。例如,idtr 的值存儲在內(nèi)存位置 loc 中:
SIDT 指令:Store Interrupt Descriptor Table Register
("sidt %0\n" : :"m"(loc));匹配(數(shù)字)約束
在某些情況下,一個變量既要充當(dāng)輸入操作數(shù),也要充當(dāng)輸出操作數(shù)。可以通過使用匹配約束。
asm ("incl %0" :"=a"(var):"0"(var));在匹配約束的示例中,寄存器 %eax 既用作輸入變量,也用作輸出變量。將 var 輸入讀取到 %eax,增加后將更新的 %eax 再次存儲在 var 中。這里的 “0” 指定和第 0 個輸出變量有相同的約束。
- 輸入從變量中讀取,或者變量被修改后,修改寫回到同一變量中
- 不需要將輸入操作數(shù)和輸出操作數(shù)的實例分開
(這是一個簡陋的版本,后面再擴充)
總結(jié)
以上是生活随笔為你收集整理的C 语言内联汇编介绍的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: python queue的用法_pyth
- 下一篇: mac os touch命令_MacOS