gcc內嵌彙編的文法介紹

來源:互聯網
上載者:User

“gcc內嵌彙編的文法”來自於哪個權威手冊?
請問:下面關於“gcc內嵌彙編的文法”來自於哪個權威手冊?我在gcc手冊中怎麼沒找到呢? 哪位大哥 能不能幫忙複製幾句英文原話過來?
多謝!

---------------------------------------------------------------------------------------------------------------------
gcc內嵌彙編簡介收藏
在內嵌彙編中,可以將C語言運算式指定為彙編指令的運算元,而且不用去管如何將C語言運算式的值讀入哪個寄存器,以及如何將計算結果寫回C 變數,你只要告訴程式中C語言運算式與彙編指令運算元之間的對應關係即可, GCC會自動插入程式碼完成必要的操作。
1、簡單的內嵌彙編
例:
__asm__ __volatile__("hlt"); "__asm__"表示後面的代碼為內嵌彙編,"asm"是"__asm__"的別名。"__volatile__"表示編譯器不要最佳化代碼,後面的指令保留原樣,"volatile"是它的別名。括弧裡面是彙編指令。
2、內嵌彙編舉例
使用內嵌彙編,要先編寫彙編指令模板,然後將C語言運算式與指令的運算元相關聯,並告訴GCC對這些操作有哪些限制條件。例如在下面的彙編語句:
__asm__ __violate__ ("movl %1,%0" : "=r" (result) : "m" (input));

"movl %1,%0"是指令模板;"%0"和"%1"代表指令的運算元,稱為預留位置,內嵌彙編靠它們將C 語言運算式與指令運算元相對應。指令模板後面用小括弧括起來的是C語言運算式,本例中只有兩個:"result"和"input",他們按照出現的順序分別與指令運算元"%0","%1"對應;注意對應順序:第一個C 運算式對應"%0";第二個運算式對應"%1",依次類推,運算元至多有10 個,分別用"%0","%1"...."%9"表示。在每個運算元前面有一個用引號括起來的字串,字串的內容是對該運算元的限制或者說要求。"result"前面的限制字串是"=r",其中"="表示"result"是輸出運算元,"r"表示需要將"result"與某個通用寄存器相關聯,先將運算元的值讀入寄存器,然後在指令中使用相應寄存器,而不是"result"本身,當然指令執行完後需要將寄存器中的值存入變數"result",從表面上看好像是指令直接對"result"進行操作,實際上GCC做了隱式處理,這樣我們可以少寫一些指令。"input"前面的"r"表示該運算式需要先放入某個寄存器,然後在指令中使用該寄存器參加運算。
C運算式或者變數與寄存器的關係由GCC自動處理,我們只需使用限制字串指導GCC如何處理即可。限制字元必須與指令對運算元的要求相匹配,否則產生的彙編代碼將會有錯,讀者可以將上例中的兩個"r",都改為"m"(m表示運算元放在記憶體,而不是寄存器中),編譯後得到的結果是:
movl input, result
很明顯這是一條非法指令,因此限制字串必須與指令對運算元的要求匹配。例如指令movl允許寄存器到寄存器,立即數到寄存器等,但是不允許記憶體到記憶體的操作,因此兩個運算元不能同時使用"m"作為限定字元。

內嵌彙編文法如下:
__asm__(彙編語句模板: 輸出部分: 輸入部分: 破壞描述部分)
共四個部分:彙編語句模板,輸出部分,輸入部分,破壞描述部分,各部分使用":"格開,彙編語句模板必不可少,其他三部分可選,如果使用了後面的部分,而前面部分為空白,也需要用":"格開,相應部分內容為空白。例如:
__asm__ __volatile__("cli": : :"memory")
1、彙編語句模板
彙編語句模板由彙編語句序列組成,語句之間使用";"、"/n"或"/n/t"分開。指令中的運算元可以使用預留位置引用C語言變數,運算元預留位置最多10 個,名稱如下:%0,%1,...,%9。指令中使用預留位置表示的運算元,總被視為long型(4個位元組),但對其施加的操作根據指令可以是字或者位元組,當把運算元當作字或者位元組使用時,預設為低字或者低位元組。對位元組操作可以顯式的指明是低位元組還是次位元組。方法是在%和序號之間插入一個字母,"b"代表低位元組,"h"代表高位元組,例如:%h1。
2、輸出部分
輸出部分描述輸出運算元,不同的運算元描述符之間用逗號格開,每個運算元描述符由限定字串和C 語言變數組成。每個輸出運算元的限定字串必須包含"="表示他是一個輸出運算元。
例:
__asm__ __volatile__("pushfl ; popl %0 ; cli":"=g" (x) )
描述符字串表示對該變數的限制條件,這樣GCC 就可以根據這些條件決定如何分配寄存器,如何產生必要的代碼處理指示運算元與C運算式或C變數之間的聯絡。
3、輸入部分
輸入部分描述輸入運算元,不同的運算元描述符之間使用逗號格開,每個運算元描述符由限定字串和C語言運算式或者C語言變數組成。
例1 :
__asm__ __volatile__ ("lidt %0" : : "m" (real_mode_idt));
例二(bitops.h):
Static __inline__ void __set_bit(int nr, volatile void * addr)
{
__asm__(
"btsl %1,%0"
:"=m" (ADDR)
:"Ir" (nr));
}
後例功能是將(*addr)的第nr位設為1。第一個預留位置%0與C 語言變數ADDR對應,第二個預留位置%1與C語言變數nr對應。因此上面的彙編語句代碼與下面的虛擬碼等價:btsl nr, ADDR,該指令的兩個運算元不能全是記憶體變數,因此將nr的限定字串指定為"Ir",將nr 與立即數或者寄存器相關聯,這樣兩個運算元中只有ADDR為記憶體變數。
4、限制字元
4.1、限制字元列表
限制字元有很多種,有些是與特定體繫結構相關,此處僅列出常用的限定字元和i386中可能用到的一些常用的限定符。它們的作用是指示編譯器如何處理其後的C語言變數與指令運算元之間的關係。
分類 限定符 描述
通用寄存器 "a" 將輸入變數放入eax
這裡有一個問題:假設eax已經被使用,那怎麼辦?
其實很簡單:因為GCC 知道eax 已經被使用,它在這段彙編代碼
的起始處插入一條語句pushl %eax,將eax 內容儲存到堆棧,然
後在這段代碼結束處再增加一條語句popl %eax,恢複eax的內容
"b" 將輸入變數放入ebx
"c" 將輸入變數放入ecx
"d" 將輸入變數放入edx
"s" 將輸入變數放入esi
"d" 將輸入變數放入edi
"q" 將輸入變數放入eax,ebx,ecx,edx中的一個
"r" 將輸入變數放入通用寄存器,也就是eax,ebx,ecx,
edx,esi,edi中的一個
"A" 把eax和edx合成一個64 位元的寄存器(use long longs)
記憶體 "m" 記憶體變數
"o" 運算元為記憶體變數,但是其定址方式是位移量類型,
也即是基址定址,或者是基址加變址定址
"V" 運算元為記憶體變數,但定址方式不是位移量類型
" " 運算元為記憶體變數,但定址方式為自動增量
"p" 運算元是一個合法的記憶體位址(指標)
寄存器或記憶體 "g" 將輸入變數放入eax,ebx,ecx,edx中的一個
或者作為記憶體變數
"X" 運算元可以是任何類型
立即數
"I" 0-31之間的立即數(用於32位移位指令)
"J" 0-63之間的立即數(用於64位移位指令)
"N" 0-255之間的立即數(用於out指令)
"i" 立即數
"n" 立即數,有些系統不支援除字以外的立即數,
這些系統應該使用"n"而不是"i"
匹配 " 0 ", 表示用它限制的運算元與某個指定的運算元匹配,
"1" ... 也即該運算元就是指定的那個運算元,例如"0"
"9" 去描述"%1"運算元,那麼"%1"引用的其實就
是"%0"運算元,注意作為限定符字母的0-9 與
指令中的"%0"-"%9"的區別,前者描述運算元,
後者代表運算元。
& 該輸出運算元不能使用過和輸入運算元相同的寄存器
運算元類型 "=" 運算元在指令中是唯寫的(輸出運算元)
"+" 運算元在指令中是讀寫類型的(輸入輸出運算元)
浮點數 "f" 浮點寄存器
"t" 第一個浮點寄存器
"u" 第二個浮點寄存器
"G" 標準的80387浮點常數
% 該運算元可以和下一個運算元交換位置
例如addl的兩個運算元可以交換順序
(當然兩個運算元都不能是立即數)
# 部分注釋,從該字元到其後的逗號之間所有字母被忽略
* 表示如果選用寄存器,則其後的字母被忽略
5、破壞描述部分
破壞描述符用於通知編譯器我們使用了哪些寄存器或記憶體,由逗號格開的字串組成,每個字串描述一種情況,一般是寄存器名;除寄存器外還有"memory"。例如:"%eax","%ebx","memory"等。
"memory"比較特殊,可能是內嵌彙編中最難懂部分。為解釋清楚它,先介紹一下編譯器的最佳化知識,再看C關鍵字volatile。最後去看該描述符。
1、編譯器最佳化介紹
記憶體訪問速度遠不及CPU處理速度,為提高機器整體效能,在硬體上引入硬體快取Cache,加速對記憶體的訪問。另外在現代CPU中指令的執行並不一定嚴格按照順序執行,沒有相關性的指令可以亂序執行,以充分利用CPU的指令流水線,提高執行速度。以上是硬體層級的最佳化。再看軟體一級的最佳化:一種是在編寫代碼時由程式員最佳化,另一種是由編譯器進行最佳化。編譯器最佳化常用的方法有:將記憶體變數緩衝到寄存器;調整指令順序充分利用CPU指令流水線,常見的是重新排序讀寫指令。對常規記憶體進行最佳化的時候,這些最佳化是透明的,而且效率很好。由編譯器最佳化或者硬體重新排序引起的問題的解決辦法是在從硬體(或者其他處理器)的角度看必須以特定順序執行的操作之間設定記憶體屏障(memory barrier),linux 提供了一個宏解決編譯器的執行順序問題。
void Barrier(void)
這個函數通知編譯器插入一個記憶體屏障,但對硬體無效,編譯後的代碼會把當前CPU寄存器中的所有修改過的數值存入記憶體,需要這些資料的時候再重新從記憶體中讀出。
2、C語言關鍵字volatile
C語言關鍵字volatile(注意它是用來修飾變數而不是上面介紹的__volatile__)表明某個變數的值可能在外部被改變,因此對這些變數的存取不能緩衝到寄存器,每次使用時需要重新存取。該關鍵字在多線程環境下經常使用,因為在編寫多線程的程式時,同一個變數可能被多個線程修改,而程式通過該變數同步各個線程,例如:
DWORD __stdcall threadFunc(LPVOID signal)
{
int* intSignal=reinterpret_cast(signal);
*intSignal=2;
while(*intSignal!=1)
sleep(1000);
return 0;
}
該線程啟動時將intSignal 置為2,然後迴圈等待直到intSignal 為1 時退出。顯然intSignal的值必須在外部被改變,否則該線程不會退出。但是實際啟動並執行時候該線程卻不會退出,即使在外部將它的值改為1,看一下對應的偽彙編代碼就明白了:
mov ax,signal
label:
if(ax!=1)
goto label
對於C編譯器來說,它並不知道這個值會被其他線程修改。自然就把它cache在寄存器裡面。記住,C 編譯器是沒有線程概念的!這時候就需要用到volatile。volatile 的本意是指:這個值可能會在當前線程外部被改變。也就是說,我們要在threadFunc中的intSignal前面加上volatile關鍵字,這時候,編譯器知道該變數的值會在外部改變,因此每次訪問該變數時會重新讀取,所作的迴圈變為如下面偽碼所示:
label:
mov ax,signal
if(ax!=1)
goto label
3、Memory
有了上面的知識就不難理解Memory修改描述符了,Memory描述符告知GCC:
1)不要將該段內嵌彙編指令與前面的指令重新排序;也就是在執行內嵌彙編代碼之前,它前面的指令都執行完畢
2)不要將變數緩衝到寄存器,因為這段代碼可能會用到記憶體變數,而這些記憶體變數會以不可預知的方式發生改變,因此GCC插入必要的代碼先將緩衝到寄存器的變數值寫回記憶體,如果後面又訪問這些變數,需要重新訪問記憶體。
如果彙編指令修改了記憶體,但是GCC 本身卻察覺不到,因為在輸出部分沒有描述,此時就需要在修改描述部分增加"memory",告訴GCC 記憶體已經被修改,GCC 得知這個資訊後,就會在這段指令之前,插入必要的指令將前面因為最佳化Cache 到寄存器中的變數值先寫回記憶體,如果以後又要使用這些變數再重新讀取。
使用"volatile"也可以達到這個目的,但是我們在每個變數前增加該關鍵字,不如使用"memory"方便

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.