2014-05-23 41 views
16

我已經看到了Arduino的代碼,並且裝配有內嵌C以外的硬件,東西線沿線的:如何使用C代碼內聯彙編工作?

asm("movl %ecx %eax"); /* moves the contents of ecx to eax */ 
__asm__("movb %bh (%eax)"); /*moves the byte from bh to the memory pointed by eax */ 

如何做到這一點其實工作?我意識到每一個編譯器都是不同的,但是這樣做的常見原因是什麼?一個人怎麼會利用這個呢?

回答

9

內聯彙編程序代碼直接進入完整的彙編代碼,並且完整無損。當你真的需要完全控制你的指令序列時,或者當你無法讓優化器在你的代碼中找到方法時,你可以這樣做。也許你需要每個時鐘滴答。也許你需要你的代碼的每一個分支採取完全相同數量的時鐘滴答,並且用NOP填充來實現這一點。

在任何情況下,很多原因爲什麼有人可能想要這樣做,但你真的需要知道你在做什麼。這些代碼對你的編譯器來說是非常不透明的,如果你做的不好,它可能不會得到任何警告。

+2

要跟進「你真的需要知道你在做什麼」。謹慎的說,您打算內聯的確切的彙編行並不總是完全由編譯器完成。我經歷了編譯器的微妙變化。編譯後總是要仔細看看反彙編,以確保它完全符合預期的要求。 – BabaBooey

+1

我相信,如果你還添加'volatile'說明符,例如'__asm__ volatile(「instrs」);' – slugonamission

+0

您還需要知道您的代碼將運行在哪個處理器(系列)上,某些情況下還需要知道哪個內存模型。幾乎任何處理器和操作系統都可以編譯ANSI-C,但是asm指令不可*便攜。 –

4

通常,編譯器會將彙編程序指令直接插入到其生成的彙編程序輸出中。它會這樣做,不考慮後果。

例如,在此代碼中,優化器正在執行復制傳播,從而它看到y = x,則z = y。所以它用z = x代替z = y,希望這可以使它進一步優化。無論如何,它並沒有發現我在此期間已經弄糟了x的價值。

char x=6; 
char y,z; 

y=x;     // y becomes 6 

_asm      
    rrncf x, 1  // x becomes 3. Optimiser doesn't see this happen! 
_endasm 

z=y;     // z should become 6, but actually gets 
        // the value of x, which is 3 

爲了解決這個問題,你基本上可以告訴優化器不要爲這個變量執行這個優化。

volatile char x=6; // Tell the compiler that this variable could change 
        // all by itself, and any time, and therefore don't 
        // optimise with it. 
char y,z; 

y=x;     // y becomes 6 

_asm      
    rrncf x, 1  // x becomes 3. Optimiser doesn't see this happen! 
_endasm 

z=y;     // z correctly gets the value of y, which is 6 
+4

請問「volatile char y,z;」防止那個優化錯誤? –

+0

@ScottSeidman:是的,特別是'volatile char y'。編譯器將確保分配實際的'y',而不是最近寫入x的優化'值,然後分配給y'。只做'z'變量不會有幫助。 –

3

ASM( 「」)__asm__都是有效的使用。基本上,如果關鍵字asm與程序中的某些內容衝突,則可以使用__asm__。如果您有多條指令,則可以在每行中用雙引號引起一個指令,並且還將該指令後綴'\ n''\ t'。這是因爲gcc會將每條指令作爲字符串發送到(GAS),並且使用換行符/標籤可以將正確格式化的行發送到彙編器。您問題中的代碼片段是基本內聯

基本內聯彙編,只有指令。在擴展組件,你還可以指定操作數。它允許你指定輸入寄存器,輸出寄存器和一列破壞寄存器。不強制指定寄存器使用,可以留給GCC和可能適合GCC的優化方案更好。對於擴展ASM的一個例子是:

__asm__ ("movl %eax, %ebx\n\t" 
      "movl $56, %esi\n\t" 
      "movl %ecx, $label(%edx,%ebx,$4)\n\t" 
      "movb %ah, (%ebx)"); 

注意,「\ n \ T」在除了最後的每一行的末尾,並在每行被封裝在引號。這是因爲gcc像前面提到的那樣將每個指令都作爲一個字符串發送。換行符/製表符組合是必需的,以便根據正確的格式將行送入。

+0

這對gcc有特別的意義嗎? –

+0

這是GNU C編譯器用作後端的**'as'**彙編器的示例。而這個assmbler使用** AT&T語法**。 – gbudan

+0

幾個更正:1)如果您正在編譯爲ISO標準,則還需要__asm__而不是asm。 2)雖然'\ t'可能是一些彙編程序需要的,但其餘部分只會讓你的彙編輸出變得很漂亮。 3)你對基本和擴展的描述是正確的,然而你爲gcc發佈的樣本是「基本的」而不是「擴展的」。請參閱https://gcc.gnu.org/onlinedocs/gcc/Using-Assembly-Language-with-C.html中基本和擴展的文檔 –

4

從歷史上看,C編譯器生成彙編代碼,然後由彙編器翻譯成機器代碼。內聯彙編是一個簡單的功能 - 在中間彙編代碼中,在這一點上,注入一些用戶選擇的代碼。一些編譯器直接生成機器碼,在這種情況下,它們包含彙編程序或調用外部彙編程序來爲內聯彙編代碼段生成機器碼。

彙編代碼最常見的用法是使用編譯器無法生成的專用處理器指令。例如,禁用關鍵部分的中斷,控制處理器功能(緩存,MMU,MPU,電源管理,查詢CPU功能等),訪問協處理器和硬件外圍設備(例如,x86上的inb/outb指令)等。很少發現asm("movl %ecx %eax"),因爲它會影響其周圍的C代碼也在使用的通用寄存器,但asm("mcr p15, 0, 0, c7, c10, 5")之類的東西也有其用處(ARM上的數據存儲器障礙)。 OSDev wiki有幾個示例代碼片段。

彙編代碼對實現破壞C的流控制模型的功能也很有用。一個常見的例子是線程之間的上下文切換(無論是協作還是搶佔,無論是否在相同的地址空間中)都需要彙編代碼來保存和恢復寄存器值。

彙編代碼也可用於手動優化內存或速度的小部分代碼。隨着編譯器變得越來越聰明,現在在應用程序級別上這很少相關,但它在大多數嵌入式世界中仍然很有用。

有兩種方法可以將裝配與C結合:通過內聯裝配,或將裝配模塊與C模塊鏈接。鏈接可以說是更乾淨但並不總是適用的:有時你需要一個函數中間的一條指令(例如,用於上下文切換的寄存器保存,函數調用會打破寄存器),或者你不想支付成本的函數調用。

大多數C編譯器都支持內聯彙編,但語法各不相同。它通常由關鍵字asm,_asm,__asm__asm__引入。除了彙編代碼本身之外,內聯彙編構造還可以包含額外的代碼,這些代碼允許您在彙編和C之間傳遞值(例如,請求將局部變量的值複製到輸入中的寄存器)或聲明彙編代碼破壞或保留某些寄存器。