C語言的本質(32)——C語言與彙編之C語言內聯彙編
用C寫程式比直接用匯編寫程式更簡潔,可讀性更好,但效率可能不如彙編程式,因為C程式畢竟要經由編譯器生成彙編程式碼,儘管現代編譯器的優化已經做得很好了,但還是不如手寫的彙編程式碼。另外,有些平臺相關的指令必須手寫,在C語言中沒有等價的語法,因為C語言的語法和概念是對各種平臺的抽象,而各種平臺特有的一些東西就不會在C語言中出現了,例如x86是埠I/O,而C語言就沒有這個概念,所以in/out指令必須用匯編來寫。
C語言簡潔易讀,容易組織規模較大的程式碼,而彙編效率高,而且寫一些特殊指令必須用匯編,為了把這兩方面的好處都佔全了,gcc提供了一種擴充套件語法可以在C程式碼中使用內聯彙編(Inline Assembly)。最簡單的格式是
__asm__("assembly code");
例如
__asm__("nop");
nop 這條指令什麼都不做,只是讓CPU空轉一個指令執行週期。如果需要執行多條彙編指令,則應該用\n\t將各條指令分隔開,例如:
__asm__("movl $1, %eax\n\t"
"movl$4, %ebx\n\t"
"int$0x80");
通常 C 程式碼中的內聯彙編需要和C的變數建立關聯,需要用到完整的內聯彙編格式:
__asm__(assembler template
:output operands /*optional */
:input operands /*optional */
:list of clobbered registers /*optional */
);
這種格式由四部分組成,第一部分是彙編指令,和上面的例子一樣,第二部分和第三部分是約束條件,第二部分指示彙編指令的運算結果要輸出到哪些C運算元中,C運算元應該是左值表示式,第三部分指示彙編指令需要從哪些C運算元獲得輸入,第四部分是在彙編指令中被修改過的暫存器列表,指示編譯器哪些暫存器的值在執行這條__asm__語句時會改變。後三個部分都是可選的,如果有就填寫,沒有就空著只寫個:號。例如:
#include <stdio.h>
int main(void)
{
int a = 10, b;
__asm__("movl%1, %%eax\n\t"
"movl%%eax, %0\n\t"
:"=r"(b) /* output */
:"r"(a) /* input */
:"%eax" /* clobbered register */
);
printf("Result:%d, %d\n", a, b);
return0;
}
這個程式將變數a的值賦給b。"r"(a)指示編譯器分配一個暫存器儲存變數a的值,作為彙編指令的輸入,也就是指令中的%1(按照約束條件的順序,b對應%0,a對應1%),至於%1究竟代表哪個暫存器則由編譯器自己決定。彙編指令首先把%1所代表的暫存器的值傳給eax(為了和%1這種佔位符區分,eax前面要求加兩個%號),然後把eax的值再傳給%0所代表的暫存器。"=r"(b)就表示把%0所代表的暫存器的值輸出給變數b。在執行這兩條指令的過程中,暫存器eax的值被改變了,所以把"%eax"寫在第四部分,告訴編譯器在執行這條__asm__語句時eax要被改寫,所以在此期間不要用eax儲存其它值。
上面的程式完成將變數a的值賦予變數b,有幾點需要說明:
1、變數b是輸出運算元,通過%0來引用,而變數a是輸入運算元,通過%1來引用。
2、輸入運算元和輸出運算元都使用r進行約束,表示將變數a和變數b儲存在暫存器中。輸入約束和輸出約束的不同點在於輸出約束多一個約束脩飾符'='。
3、在內聯彙編語句中使用暫存器eax時,暫存器名前應該加兩個'%',即%%eax。內聯彙編中使用%0、%1等來標識變數,任何只帶一個'%'的識別符號都看成是運算元,而不是暫存器。
內聯彙編語句的最後一個部分告訴GCC它將改變暫存器eax中的值,GCC在處理時不應使用該暫存器來儲存任何其它的值。
4、由於變數b被指定成輸出運算元,當內聯彙編語句執行完畢後,它所儲存的值將被更新。
5、在內聯彙編中用到的運算元從輸出部的第一個約束開始編號,序號從0開始,每個約束記數一次,指令部要引用這些運算元時,只需在序號前加上'%'作為字首就可以了。
我們看一下這個程式的反彙編結果:
__asm__("movl %1, %%eax\n\t"
80483dc: 8b 55 f8 mov -0x8(%ebp),%edx
80483df: 89 d0 mov %edx,%eax
80483e1: 89 c2 mov %eax,%edx
80483e3: 89 55 f4 mov %edx,-0xc(%ebp)
"movl %%eax, %0\n\t"
:"=r"(b) /* output */
:"r"(a) /* input */
:"%eax" /* clobbered register */
);
可見%0和%1都代表edx暫存器,首先把變數a(位於ebp-8的位置)的值傳給edx然後執行內聯彙編的兩條指令,然後把edx的值傳給b(位於ebp-12的位置)。
有關內聯彙編,通常情況下只需要瞭解這些就足夠了。
相關文章
- 09. C語言內嵌彙編程式碼C語言
- C 語言宏 + 內聯彙編實現 MIPS 系統呼叫
- 組合語言-019(彙編程式與c\c++相互呼叫)組合語言C++
- c語言if語句是如何變成彙編程式碼的?C語言
- C語言知識彙總 | 00-C語言知識彙總目錄C語言
- c語言指標彙總C語言指標
- C語言 - 條件編譯C語言編譯
- C語言與嵌入式C語言的區別C語言
- C語言知識彙總 | 51-C語言字串指標(指向字串的指標)C語言字串指標
- go語言與c語言的相互呼叫GoC語言
- c語言多檔案編譯C語言編譯
- C語言 編寫線段樹C語言
- C語言知識彙總 | 56-C語言NULL空指標以及void指標C語言Null指標
- C語言C語言
- 聊聊C語言/C++—程式和程式語言C語言C++
- 【開發語言】PHP、Java、C語言的編譯執行過程PHPJavaC語言編譯
- 使用 Sublime Text 3 編譯 C 語言編譯
- 3- C語言編譯過程C語言編譯
- C語言編譯器手機版C語言編譯
- Linux下C語言編譯的問題LinuxC語言編譯
- 通訊錄的c語言程式編輯C語言
- C語言字串C語言字串
- C語言(一)C語言
- C語言: returnC語言
- C語言 typedefC語言
- C語言學習方法,怎麼學習C語言?C語言
- 【C語言】linux下多檔案編譯C語言Linux編譯
- 現代編譯原理C語言描述pdf編譯原理C語言
- 嵌入式c語言編碼規範C語言
- C語言-->(十四)結構體、巨集、編譯C語言結構體編譯
- Android-NDK-11-C語言編譯原理AndroidC語言編譯原理
- C# 語言歷史版本特性(C# 1.0到C# 8.0彙總)C#
- C#語言歷史版本特性(C# 1.0到C# 8.0彙總)C#
- ARM彙編和C語言混合程式設計中陣列的陣列的操作C語言程式設計陣列
- 1901:The C programming language !(C語言)C語言
- C語言教程——03 C語言結構C語言
- C語言:extern用法C語言
- C語言版本迭代C語言
- C語言 截圖C語言