内联汇编

维基百科,自由的百科全书
跳转至: 导航搜索

内联汇编,(英语Assembly language,中国大陆譯作內聯彙編,台湾、港澳譯作行内组语) 是由部分編譯器支援的一種功能。其將非常低階的組合語言內嵌在高階語言源始碼中。實施行內組語通常是為了以下理由:

  • 執行效率最佳化:將演算法中最攸關效能的部份使用手寫組語取代高階程式碼,優點是不會受到編譯器的限制。
  • 使用處理器特有指令:某些處理器提供特定的指令,比如Compare-and-swap和Test-and-set指令可以直接用以實作信號機制。因為多工系統都需要信號機制,幾乎所有現代的處理器都支援前述的兩個指令。其它的一些指令集如SPARC架構的Visual Instruction Set指令集Intel處理器的MMX指令集以及SSE指令集等。
  • 系統調用(System Call):高階語言鮮少提供直接調用system calls的機制,故使用組語來進行這項工作。

使用處理器持有指令優化範例[编辑]

下面是一段在D語言進行行內組語的程式碼。該程式碼使用x86架構浮點運算器指令來計算\tan x。此實作快於編譯器產生的一系列浮點數運算,行內組語也使編程人員得以使用fldpi指令來載入在x86架構下可得到的最佳之pi估計值。

// 計算 tan(x)
real tan(real x)
{
   asm
   {
       fld     x[EBP]                  ; // 將x值載入浮點運算器的堆疊上
       fxam                            ; // 測試堆疊頂端的值是否是合法、可計算浮點數
       fstsw   AX                      ;
       sahf                            ;
       jc      trigerr                 ; // 若 x 是NAN,正負無限,或空值 
                                         // 387可以處理denormals數值
SC18:  fptan                           ; // 為與8087運算器相容 fptan 會使堆疊頂端為 1.0, 再來才是 tan值,
       fstp    ST(0)                   ; // 丟棄堆疊頂端的值
       fstsw   AX                      ;
       sahf                            ;
       jnp     Lret                    ; // C2 為 FPU 狀態變數, C2 == 1 表示 x 超出允許的範圍
                                       ; // tan 之週期為 pi ,下面的程式碼就是將 x 縮至允許的範圍
       fldpi                           ;
       fxch                            ;
SC17:  fprem1                          ;
       fstsw   AX                      ;
       sahf                            ;
       jp      SC17                    ;
       fstp    ST(1)                   ; // 將 pi 值移出堆疊
       jmp     SC18                    ;
   }
trigerr:
   return real.nan;
Lret:
   ;
}

系統調用(system calls)範例[编辑]

保護模式運行的應用程式無法直接呼叫專屬於OS的功能。因為OS的行程空間包含核心空間(kernel mode)用戶空間(user mode);運行在用戶空間的程式只能透過中斷來引用專屬於作業系統的功能。通常高階語言都不提供這項功能,所以要運用行內組語將呼叫system calls的過程包裝為高階語言可辨認、呼叫的函式。

下面的C語言片段即含有一個system call的包裝函式。其組語語法為GNU組譯器使用的AT&T 組語語法。一般這類程式都會使用巨集實作,不過為了清楚展示觀念,在此列出了完整的程式碼。

GNU組譯器的行內組語語法相當直覺,基本型式如下:

asm("assembly code");

例如:

asm("movl %ecx, %eax"); /* 複製ecx暫存器的內容至eax暫存器 */

__asm__("movb %bh, (%eax)"); /* 從bh暫存器複製1位元組的資料到eax暫存器所指的記憶體區塊 */

必須注意的是,AT&T語法中的運算元順序與Windows平台下常用的MASM剛好相反。

asm__asm__都是合法型式;當asm與程式碼中某些變數命名起衝突時可使用後者。

extern int errno;
 
int funcname(int arg1, int *arg2, int arg3)
{
  int res;
  __asm__ volatile(
    "int $0x80"        /* 向 OS 拋出請求 */
    : "=a" (res)       /* 請編譯器將結果將儲存於 eax ("a") */
      "+b" (arg1),     /* 請編譯器將 arg1 存於 ebx ("b") */
      "+c" (arg2),     /* 請編譯器將 arg2 存於 ecx ("c") */
      "+d" (arg3)      /* 請編譯器將 arg3 存於 edx ("d") */
    : "a"  (128)       /* 該 system call 的編號放於 eax ("a") */
    : "memory", "cc"); /* 通知編譯器,記憶體與 condition code register (決定分支的暫存器) 己被更改 */
 
  /* 若 OS 回傳負值表示錯誤;則包裝函式要設定 errno global variable 並回傳 -1 */
  if (-125 <= res && res < 0) {
    errno = -res;
    res   = -1;
  }  
  return res;
}

外部链接[编辑]