有時候我們希望在C/C++代碼中使用嵌入式匯編,因?yàn)镃中沒有對應(yīng)的函數(shù)或語法可用。比如我最近在ARM上寫FIR程序時,需要對最后的結(jié)果進(jìn)行飽和處理,但gcc沒有提供ssat這樣的函數(shù),于是不得不在C代碼中嵌入?yún)R編指令。
1. 入門
在C中嵌入?yún)R編的最大問題是如何將C語言變量與指令操作數(shù)相關(guān)聯(lián)。當(dāng)然,gcc都幫我們想好了。下面是是一個簡單例子。
asm(“fsinx %1, %0”:”=f”(result):”f”(angle));
這里我們不需要關(guān)注fsinx指令是干啥的;只需要知道這條指令需要兩個浮點(diǎn)寄存器作為操作數(shù)。作為專職處理C語言的gcc編譯器,它是沒辦法知道fsinx這條匯編指令需要什么樣的操作數(shù)的,這就要求程序猿告知gcc相關(guān)信息,方法就是指令后面的”=f”和”f”,表示這是兩個浮點(diǎn)寄存器操作數(shù)。這被稱為操作數(shù)規(guī)則(constraint)。規(guī)則前面加上”=”表示這是一個輸出操作數(shù),否則是輸入操作數(shù)。constraint后面括號內(nèi)的是與該寄存器關(guān)聯(lián)的變量。這樣gcc就知道如何將這條嵌入式匯編語句轉(zhuǎn)成實(shí)際的匯編指令了:
fsinx:匯編指令名
%1, %0:匯編指令操作數(shù)
“=f”(result):操作數(shù)%0是一個浮點(diǎn)寄存器,與變量result關(guān)聯(lián)(對輸出操作數(shù),“關(guān)聯(lián)”的意思就是說gcc執(zhí)行完這條匯編指令后會把寄存器%0的內(nèi)容送到變量result中)
“f”(angle):操作數(shù)%1是一個浮點(diǎn)寄存器,與變量angle關(guān)聯(lián)(對輸入操作數(shù),“關(guān)聯(lián)”的意思是就是說gcc執(zhí)行這條匯編指令前會先將變量angle的值讀取到寄存器%1中)
因此這條嵌入式匯編會轉(zhuǎn)換為至少三條匯編指令(非優(yōu)化):
將angle變量的值加載到寄存器%1
fsinx匯編指令,源寄存器%1,目標(biāo)寄存器%0
將寄存器%0的值存儲到變量result
當(dāng)然,在高優(yōu)化級別下上面的敘述可能不適用;比如源操作數(shù)可能本來就已經(jīng)在某個浮點(diǎn)寄存器中了。
這里我們也看到constraint前加”=”符號的意義:gcc需要知道這個操作數(shù)是在執(zhí)行嵌入?yún)R編前從變量加載到寄存器,還是在執(zhí)行后從寄存器存儲到變量中。
常用的constraints有以下幾個(更多細(xì)節(jié)參見gcc手冊):
m 內(nèi)存操作數(shù)
r 寄存器操作數(shù)
i 立即數(shù)操作數(shù)(整數(shù))
f 浮點(diǎn)寄存器操作數(shù)
F 立即數(shù)操作數(shù)(浮點(diǎn))
從這個栗子也可以看出嵌入式匯編的基本格式:
asm(“匯編指令”:”=輸出操作數(shù)規(guī)則”(關(guān)聯(lián)變量):”輸入操作數(shù)規(guī)則”(關(guān)聯(lián)變量));
輸出操作數(shù)必須為左值;這個顯然。
2. 多個操作數(shù),或沒有輸出操作數(shù)
如果某個指令有多個輸入或輸出操作數(shù)怎么辦?例如arm有很多指令是三操作數(shù)指令。這個時候用逗號分隔多個規(guī)則:
asm(“add %0, %1, %2”:”=r”(sum):”r”(a), “r”(b));
每條操作數(shù)規(guī)則按順序?qū)?yīng)操作數(shù)%0, %1, %2。
對于沒有輸出操作數(shù)的情況,在匯編指令后就沒有輸出規(guī)則,于是就出現(xiàn)兩個連續(xù)冒號,后跟輸入規(guī)則。
3. 輸入-輸出(或讀-寫)操作數(shù)
有時候一個操作數(shù)既是輸入又是輸出,比如x86下的這條指令:
add %eax, %ebx
注意指令使用AT&T格式而不是Intel格式。寄存器ebx同時作為輸入操作數(shù)和輸出操作數(shù)。對這樣的操作數(shù),在規(guī)則前使用”+”字符:
asm("add %1, %0" : "+r"(a) : "r"(b));
對應(yīng)C語言語句a=a+b。
注意這樣的操作數(shù)不能使用”=”符號,因?yàn)間cc看到”=”符號會認(rèn)為這是一個單輸出操作數(shù),于是在將嵌入?yún)R編轉(zhuǎn)換為真正匯編的時候就不會預(yù)先將變量a的值加載到寄存器%0中。
另一個辦法是將讀-寫操作數(shù)在邏輯上拆分為兩個操作數(shù):
asm(“add %2, %0” : “=r”(a) : “0”(a), “r”(b));
對“邏輯”輸入操作數(shù)1指定數(shù)字規(guī)則”0”,表示這個邏輯操作數(shù)占用和操作數(shù)0一樣的“位置”(占用同一個寄存器)。這種方法的特點(diǎn)是可以將兩個“邏輯”操作數(shù)關(guān)聯(lián)到兩個不同的C語言變量上:
asm("add %2, %0" : "=r"(c) : "0"(a), "r"(b));
對應(yīng)于C程序語句c=a+b。
數(shù)字規(guī)則僅能用于輸入操作數(shù),且必須引用到輸出操作數(shù)。拿上例來說,數(shù)字規(guī)則”0”位于輸入規(guī)則段,且引用到輸出操作數(shù)0,該數(shù)字規(guī)則自身占用操作數(shù)計(jì)數(shù)1。
這里要注意,通過同名C語言變量是無法保證兩個操作數(shù)占用同一“位置”的。比如下面這樣的寫法是不行的:
(錯誤寫法)asm(“add %2, %0”:”=r”(a):”r”(a), “r”(b));
4. 指定寄存器
有時候我們需要在指令中使用指定的寄存器;典型的栗子是系統(tǒng)調(diào)用,必須將系統(tǒng)調(diào)用碼和參數(shù)放在指定寄存器中。為了達(dá)到這個目的,我們要在聲明變量時使用擴(kuò)展語法:
register int a asm(“%eax”) = 1; // statement 1
register int b asm(“%ebx”) = 2; // statement 2
asm("add %1, %0" : "+r"(a) : "r"(b)); // statement 3
注意只有在執(zhí)行匯編指令時能確定a在eax中,b在ebx中,其他時候a和b的存放位置是不可知的。
另外,在這么用的時候要注意,防止statement 2在執(zhí)行時覆蓋了eax。例如statement 2改成下面這句:
register int b asm(“%ebx”) = func();
函數(shù)調(diào)用約定會將func()的返回值放在eax里,于是破壞了statement 1對a的賦值。這個時候可以先用一條語句將func返回值放在臨時變量里:
int t = func();
register int a asm(“%eax”) = 1; // statement 1
register int b asm(“%ebx”) = t; // statement 2
asm("add %1, %0" : "+r"(a) : "r"(b)); // statement 3
5. 隱式改變寄存器
有的匯編指令會隱含修改一些不在指令操作數(shù)中的寄存器,為了讓gcc知道這個情況,將隱式改變寄存器規(guī)則列在輸入規(guī)則之后。下面是VAX機(jī)上的栗子:
asm volatile(“movc3 %0,%1,%2”
: /* no outputs */
:”g”(from),”g”(to),”g”(count)
:”r0”,”r1”,”r2”,”r3”,”r4”,”r5”);
(movc3是一條字符塊移動(Move characters)指令)
這里要注意的是輸入/輸出規(guī)則中列出的寄存器不能和隱含改變規(guī)則中的寄存器有交叉。比如在上面的栗子里,規(guī)則“g”中就不能包含r0-r5。以指定寄存器語法聲明的變量,所占用的寄存器也不能和隱含改變規(guī)則有交叉。這個應(yīng)該好理解:隱含改變規(guī)則是告訴gcc有額外的寄存器需要照顧,自然不能和輸入/輸出寄存器有交集。
另外,如果你在指令里顯式指定某個寄存器,那么這個寄存器也必須列在隱式改變規(guī)則之中(有點(diǎn)繞了哈)。上面我們說過gcc自身是不了解匯編指令的,所以你在指令中顯式指定的寄存器,對gcc來說是隱式的,因此必須包含在隱式規(guī)則之中。另外,指令中的顯式寄存器前需要一個額外的%,比如%%eax。
6. volatile
asm volatile通知gcc你的匯編指令有side effect,千萬不要給優(yōu)化沒了,比如上面的栗子。
如果你的指令只是做些計(jì)算,那么不需要volatile,讓gcc可以優(yōu)化它;除此以外,無腦給每個asm加上volatile或者是個好辦法。
-
寄存器
+關(guān)注
關(guān)注
31文章
5421瀏覽量
123290 -
內(nèi)存
+關(guān)注
關(guān)注
8文章
3108瀏覽量
74979 -
C語言
+關(guān)注
關(guān)注
180文章
7630瀏覽量
140207 -
GCC
+關(guān)注
關(guān)注
0文章
109瀏覽量
25229
原文標(biāo)題:byeyear: gcc內(nèi)嵌匯編詳解
文章出處:【微信號:LinuxDev,微信公眾號:Linux閱碼場】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。
發(fā)布評論請先 登錄
ARM指令集的格式與操作數(shù)符號簡析
IBM-PC匯編語言指令集
駕駛操作數(shù)據(jù)采集系統(tǒng)設(shè)計(jì)研究
PLC的相關(guān)指令詳細(xì)介紹

單片機(jī)尋找操作數(shù)存放單元地址的方法解析

使用單片機(jī)實(shí)現(xiàn)兩按鍵加減操作數(shù)碼管顯示的C語言程序免費(fèi)

評論