內(nèi)聯(lián)匯編和嵌入型匯編的使用
大?。?/span>0.4 MB 人氣: 2017-10-19 需要積分:1
標(biāo)簽:內(nèi)聯(lián)匯編(6450)
??內(nèi)聯(lián)匯編和嵌入型匯編是包含在C‘ target=’_blank‘ style=’cursor:pointer;color:#D05C38;text-decoration:underline;‘》C/C++編譯器中的匯編器。使用它可以在C/C++程序中實(shí)現(xiàn)C/C++語言不能完成的一些工作。例如,在下面幾種情況中必須使用內(nèi)聯(lián)匯編或嵌入型匯編。· 程序中使用飽和算術(shù)運(yùn)算(Saturating arithmetic),如SSAT16 和 USAT16指令。
· 程序中需要對協(xié)處理器進(jìn)行操作。
· 在C或C++程序中完成對程序狀態(tài)寄存器的操作。
使用內(nèi)聯(lián)匯編編寫的程序代碼效率也比較高。
12.1.1 內(nèi)聯(lián)匯編
1.內(nèi)聯(lián)匯編語法
內(nèi)聯(lián)匯編使用“_asm”(C++)和“asm”(C和C++)關(guān)鍵字聲明,語法格式如下所示。
· __asm(“instruction[;instruction]”); // 必須為單條指令
__asm{instruction[;instruction]}
· __asm{
。..
instruction
。..
}
· asm(“instruction[;instruction]”); // 必須為單條指令
asm{instruction[;instruction]}
· asm{
。..
instruction
。..
}
內(nèi)聯(lián)匯編支持大部分的ARM指令,但不支持帶狀態(tài)轉(zhuǎn)移的跳轉(zhuǎn)指令,如BX和BLX指令,詳見ARM相關(guān)文檔。
由于內(nèi)聯(lián)匯編嵌入在C或C++程序中,所有在用法上有其自身的一些特點(diǎn)。
① 如果同一行中包含多條指令,則用分號隔開。
?、?如果一條指令不能在一行中完成,使用反斜杠“/”將其連接。
?、?內(nèi)聯(lián)匯編中的注釋語句可以使用C或C++風(fēng)格的。
?、?匯編語言中使用逗號“,”作為指令操作數(shù)的分隔符,所以如果在C語言中使用逗號必須用圓括號括起來。如,__asm {ADD x, y, (f(), z)}。
?、?內(nèi)聯(lián)匯編語言中的寄存器名被編譯器視為C或C++語言中的變量,所以內(nèi)聯(lián)匯編中出現(xiàn)的寄存器名不一定和同名的物理寄存器相對應(yīng)。這些寄存器名在使用前必須聲明,否則編譯器將提示警告信息。
?、?內(nèi)聯(lián)匯編中的寄存器(除程序狀態(tài)寄存器CPSR和SPSR外)在讀取前必須先賦值,否則編譯器將產(chǎn)生錯誤信息。下面的例子顯示了內(nèi)聯(lián)匯編和真正匯編的區(qū)別。
錯誤的內(nèi)聯(lián)匯編函數(shù)如下所示。
int f(int x)
{
__asm
{
STMFD sp!, {r0} // 保存r0不合法,因?yàn)樵谧x之前沒有對寄存器寫操作
ADD r0, x, 1
EOR x, r0, x
LDMFD sp!, {r0} // 不需要恢復(fù)寄存器
}
return x;
}
將其進(jìn)行改寫,使它符合內(nèi)聯(lián)匯編的語法規(guī)則。
int f(int x)
{
int r0;
__asm
{
ADD r0, x, 1
EOR x, r0, x
}
return x;
}
下面通過幾個例子進(jìn)一步了解內(nèi)聯(lián)匯編的語法。
① 字符串拷貝
下面的例子使用一個循環(huán)完成了字符串的拷貝工作。
#include 《stdio.h》
void my_strcpy(const char *src, char *dst)
{
int ch;
__asm
{
loop:
LDRB ch, [src], #1
STRB ch, [dst], #1
CMP ch, #0
BNE loop
}
}
int main(void)
{
const char *a = “Hello world!”;
char b[20];
my_strcpy (a, b);
printf(“Original string: ’%s‘\n”, a);
printf(“Copied string: ’%s‘\n”, b);
return 0;
}
?、?中斷使能
下面的例子通過讀取程序狀態(tài)寄存器CPSR并設(shè)置它的中斷使能位bit[7]來禁止/打開中斷。需要注意的是,該例只能運(yùn)行在系統(tǒng)模式下,因?yàn)橛脩裟J绞菬o權(quán)修改程序狀態(tài)寄存器的。
__inline void enable_IRQ(void)
{
int tmp;
__asm
{
MRS tmp, CPSR
BIC tmp, tmp, #0x80
MSR CPSR_c, tmp
}
}
__inline void disable_IRQ(void)
{
int tmp;
__asm
{
MRS tmp, CPSR
ORR tmp, tmp, #0x80
MSR CPSR_c, tmp
}
}
int main(void)
{
disable_IRQ();
enable_IRQ();
}
③ 分隔符的計算
下面的例子計算兩個整數(shù)數(shù)組中分隔符“,”的個數(shù)。該例子顯示了如何在內(nèi)聯(lián)匯編中訪問C或C++語言中的數(shù)據(jù)類型。該例中的內(nèi)聯(lián)匯編函數(shù)mlal()被編譯器優(yōu)化為一條SMLAL指令,可以使用-S –interleave編譯選項(xiàng)使編譯器輸出匯編結(jié)果。
#include 《stdio.h》
/* change word order if big-endian */
#define lo64(a) (((unsigned*) &a)[0]) /* long long型的低32位 */
#define hi64(a) (((int*) &a)[1]) /* long long型的高32位 */
__inline __int64 mlal(__int64 sum, int a, int b)
{
#if !defined(__thumb) && defined(__TARGET_FEATURE_MULTIPLY)
__asm
{
SMLAL lo64(sum), hi64(sum), a, b
}
#else
sum += (__int64) a * (__int64) b;
#endif
return sum;
}
__int64 dotprod(int *a, int *b, unsigned n)
{
__int64 sum = 0;
do
sum = mlal(sum, *a++, *b++);
while (--n != 0);
return sum;
}
int a[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
int b[10] = { 10, 9, 8, 7, 6, 5, 4, 3, 2, 1 };
int main(void)
{
printf(“Dotproduct %lld (should be %d)\n”, dotprod(a, b, 10), 220);
return 0;
}
2.內(nèi)聯(lián)匯編中的限制
可以在內(nèi)聯(lián)匯編代碼中執(zhí)行的操作有許多限制。這些限制提供安全的方法,并確保在匯編代碼中不違反 C 和 C++ 代碼編譯中的假設(shè)。
① 不能直接向程序計數(shù)器PC賦值。
?、?內(nèi)聯(lián)匯編不支持標(biāo)號變量。
?、?不能在程序中使用“?!被騵PC}得到當(dāng)前指令地址值。
?、?在16進(jìn)制常量前加“0x”代替“&”。
?、?建議不要對堆棧進(jìn)行操作。
?、?編譯器可能會使用r12和r13寄存器存放編譯的中間結(jié)果,在計算表達(dá)式值時可能會將寄存器r0~r3、r12及r14用于子程序調(diào)用。另外在內(nèi)聯(lián)匯編中設(shè)置程序狀態(tài)寄存器CPSR中的標(biāo)志位NZCV時,要特別小心,內(nèi)聯(lián)匯編中的設(shè)置很可能會和編譯器計算的表達(dá)式的結(jié)果沖突。
?、?用內(nèi)聯(lián)匯編代碼更改處理器模式是可能的。然而,更改處理器模式會禁止使用 C或 C++操作數(shù)或禁止對已編譯C或C++代碼的調(diào)用,直到將處理器模式更改回原設(shè)置之后之前的函數(shù)庫才可正常使用。
⑧ 為Thumb狀態(tài)編譯C或C++時,內(nèi)聯(lián)匯編程序不可用且不匯編Thumb指令。
?、?盡管可以使用通用協(xié)處理器指令指定 VFP 或 FPA 指令,但內(nèi)聯(lián)匯編程序不為它們提供直接支持。
不能用內(nèi)聯(lián)匯編代碼更改 VFP 向量模式。內(nèi)聯(lián)匯編可包含浮點(diǎn)表達(dá)式操作數(shù),該操作數(shù)可使用編譯程序生成的 VFP 代碼求出操作數(shù)值。因此,僅由編譯程序修改 VFP 狀態(tài)很重要。
?、?內(nèi)嵌匯編不支持的指令有BX、BLX、BXJ和BKPT指令。而LDM、STM、LDRD和STRD指令可能被等效為ARM LDR或STR指令。
3.內(nèi)聯(lián)匯編中的虛擬寄存器
內(nèi)聯(lián)匯編程序提供對 ARM 處理器物理寄存器的非直接訪問。如果在內(nèi)聯(lián)匯編程序指令中將某個ARM寄存器用作操作數(shù),它就成為相同名稱的虛擬寄存器的引用,而不是對實(shí)際物理ARM寄存器的引用。例如內(nèi)聯(lián)匯編指令中使用了寄存器r0,但對于C編譯器,指令中出現(xiàn)的r0只是一個變量,并非實(shí)際的物理寄存器r0,當(dāng)程序運(yùn)行時,可能是由物理寄存器r1來存放r0所代表的值。
下面的例子顯示了編譯器如何對內(nèi)聯(lián)匯編指令的寄存器進(jìn)行分配。
程序的源代碼如下。
#include 《stdio.h》
void test_inline_register(void)
{
int i;
int r5,r6,r7;
__asm
{
MOV i,#0
loop:
MOV r5,#0
MOV r6,#0
MOV r7,#0
ADD i,i,#1
CMP i,#3
BNE loop
}
}
int main(void)
{
test_inline_register ();
printf(“test inline register\n”);
return 0;
}
由C編譯器編譯出的匯編碼如下所示。
test_inline_register:
0000807C E3A00000 MOV r0,#0
》》》 TEST_INLINE_REGISTER\#12 loop:
00008080 E1A00000 NOP
》》》 TEST_INLINE_REGISTER\#13 MOV r5,#0
00008084 E3A01000 MOV r1,#0
》》》 TEST_INLINE_REGISTER\#14 MOV r6,#0
00008088 E3A02000 MOV r2,#0
》》》 TEST_INLINE_REGISTER\#15 MOV r7,#0
0000808C E3A03000 MOV r3,#0
》》》 TEST_INLINE_REGISTER\#16 ADD i,i,#1
00008090 E2800001 ADD r0,r0,#1
》》》 TEST_INLINE_REGISTER\#17 CMP i,#3
00008094 E3500003 CMP r0,#3
00008098 0A000000 BEQ 0x80a0 《TEST_INLINE_REGISTER\#21》
》》》 TEST_INLINE_REGISTER\#18 BNE loop
0000809C EAFFFFF8 B 0x8084 《TEST_INLINE_REGISTER\#13》
》》》 TEST_INLINE_REGISTER\#21 }
000080A0 E12FFF1E BX r14
》》》 TEST_INLINE_REGISTER\#25 {
注意下面的代碼是由Realview2.2編譯出的代碼,使用其他編譯器結(jié)果可能有差異。同一段內(nèi)嵌匯編經(jīng)過不同版本的編譯器編譯后,在指令里可能使用不一樣的實(shí)際寄存器,但是只要遵循文檔里的編碼指導(dǎo),執(zhí)行的功能肯定相同。
例子中以“》》》”的開頭的行是程序的源碼部分,緊接其后的是由編譯器編譯出的匯編代碼。從上例可以很清楚地看出,源程序中使用了r5、r6和r7,但由編譯器編譯后的代碼使用了寄存器r1、r2和r3。
另外,需要特別指出的是在內(nèi)聯(lián)匯編中使用寄存器必須先聲明其變量類型,如上例中的“int r5,r6,r7”。如果不在使用前進(jìn)行聲明,編譯器將給出以下錯誤信息。
#1267-D: Implicit physical register R3 should be defined as a variable
編譯程序定義的虛擬寄存器有函數(shù)局部作用范圍,即在同一個C函數(shù)中,涉及相同虛擬寄存器名稱的多個asm語句或聲明,訪問相同的虛擬寄存器。
內(nèi)聯(lián)匯編沒有為pc(r15)、lr(r14)和sp(r13)寄存器創(chuàng)建虛擬寄存器,而且不能在內(nèi)聯(lián)匯編代碼中讀取或直接修改它們的值。如果內(nèi)聯(lián)匯編程序中出現(xiàn)了對這些寄存器的訪問,編譯器將給出以下錯誤消息。例如,如果指定r14:
#20: identifier “r14” is undefined
內(nèi)聯(lián)匯編可以直接使用CPSR和SPSR對程序狀態(tài)字進(jìn)行操作,因?yàn)閮?nèi)聯(lián)匯編中不存在虛擬處理器狀態(tài)寄存器(PSR)。任何對 PSR 的引用總是指向物理 PSR。
4.內(nèi)聯(lián)匯編中的指令展開
內(nèi)聯(lián)匯編代碼中的ARM指令可能會在編譯過程中擴(kuò)展為幾條指令。擴(kuò)展取決于指令、指令中指定的操作數(shù)個數(shù)以及每個操作數(shù)的類型和值。通常,被擴(kuò)展的指令有以下兩種情況:
· 含有常數(shù)操作的指令;
· LDM、STM、LDRD 和 STRD指令;
· 乘法指令MUL被擴(kuò)展為一系列的加法和移位指令。
下面的例子說明了編譯器如何對含有常數(shù)操作的指令進(jìn)行擴(kuò)展。
包含有常數(shù)操作的加法指令:
ADD r0,r0,#1023
被編譯器編譯為如下兩條指令:
ADD r0,r0,#1024
SUB r0,r0,#1
注意擴(kuò)展指令對程序狀態(tài)寄存器CPSR的影響:算術(shù)指令影響相應(yīng)的NZCV標(biāo)準(zhǔn)位;其他指令設(shè)置NZ標(biāo)志位不影響V標(biāo)志位。
所有的LDM和STM指令被擴(kuò)展為等效的LDR和STR指令序列。然而,在優(yōu)化過程中,編譯程序可能因此將單獨(dú)的指令重組為一條LDM或STM指令。
5.內(nèi)聯(lián)匯編中的常數(shù)
指令中的標(biāo)志符“?!笔强蛇x的(前面的例子中,指令中常數(shù)前均加了標(biāo)志符“?!保?。如果在指令中使用了“?!保瑒t其后的表達(dá)式必為常數(shù)。
6.內(nèi)聯(lián)匯編指令對標(biāo)志位的影響
內(nèi)聯(lián)匯編指令可能顯式或隱式地更新處理器程序狀態(tài)寄存器的條件標(biāo)志位。在僅包含虛擬寄存器操作數(shù)或簡單表達(dá)式操作數(shù)的內(nèi)聯(lián)匯編中,其執(zhí)行結(jié)果是可以預(yù)見。如果指令中指定了隱式或顯式更新條件標(biāo)志位,則條件標(biāo)志位根據(jù)指令的執(zhí)行進(jìn)行設(shè)置。如果未指定更新,則條件標(biāo)志不會更改。如果內(nèi)嵌匯編指令的操作數(shù)都不是簡單操作數(shù)時或指令不顯式更新條件標(biāo)志位,則條件標(biāo)志位可能會被破壞。一般情況下,編譯程序不易診斷出對條件標(biāo)志的潛在破壞。然而,在構(gòu)造析構(gòu)C++臨時函數(shù)的操作數(shù)時,如果指令試圖更新條件標(biāo)志,編譯程序?qū)⒔o予警告,因?yàn)槲鰳?gòu)函數(shù)可能會破壞條件標(biāo)志位。
7.內(nèi)聯(lián)匯編指令中的操作數(shù)
內(nèi)聯(lián)匯編指令中的操作數(shù)分為以下4種。
· 虛擬寄存器
· 表達(dá)式操作數(shù)
· 寄存器列表
· 中間操作數(shù)
?。?)虛擬寄存器
在內(nèi)聯(lián)匯編指令中指定的寄存器表示虛擬寄存器而不是實(shí)際的物理寄存器。由編譯器編譯的匯編代碼中使用的物理寄存器可能與在指令中指定的不同。每個虛擬寄存器的初值是不可預(yù)測的,必須在讀取之前將初值寫入虛擬寄存器。如果在寫入之前試圖讀虛擬寄存器,編譯程序會給予警告。
?。?)表達(dá)式操作數(shù)
在內(nèi)聯(lián)匯編指令中,可將函數(shù)自變量、C或C++變量和其他C或C++表達(dá)式指定為寄存器操作數(shù)。用作操作數(shù)的表達(dá)式必須為整數(shù)類型,如char、short、int或long,(長整型long long除外)或指針類型。當(dāng)表達(dá)式作為內(nèi)聯(lián)匯編指令的操作數(shù)時,編譯器在編譯時自動增加一段代碼計算表示式的值并將其加載到指定的寄存器中。
注意數(shù)據(jù)類型中除char和short(默認(rèn)為無符號類型)外,其他均為有符號類型。
下面的例子顯示了編譯器如何處理內(nèi)聯(lián)匯編中的表達(dá)式操作數(shù)。
程序源代碼如下所示。
/* Example Operands */
void my_operand(void)
{
int i,j,total;
__asm
{
mov i,#0
mov j,#1
add total,j,i+j
}
}
int main(void)
{
my_operand ();
}
由編譯器編譯出的匯編代碼如下所示(其中只列出了內(nèi)聯(lián)匯編的一段代碼)。
my_operand:
0000807C E3A01000 MOV r1,#0
》》》 OPERANDS\#12 mov j,#1
00008080 E3A00001 MOV r0,#1
00008084 E0812000 ADD r2,r1,r0
》》》 OPERANDS\#13 add total,j,i+j
00008088 E0803002 ADD r3,r0,r2
》》》 OPERANDS\#15 }
0000808C E12FFF1E BX r14
》》》 OPERANDS\#19 {
從編譯的代碼可以看出,編譯器將“add total,j,i+j”分為兩步來完成,用戶在編寫自己的內(nèi)聯(lián)匯編應(yīng)用程序時要特別注意這一點(diǎn)。
包含多個表達(dá)式操作數(shù)的指令,沒有指定表達(dá)式操作數(shù)求值的順序。
將C或C++表達(dá)式用作內(nèi)聯(lián)匯編程序操作數(shù),如果表達(dá)式的值不能滿足 ARM指令中所要求的指令操作數(shù)約束條件,一條指令將被擴(kuò)展為多條指令。
如果用作操作數(shù)的表達(dá)式創(chuàng)建需要析構(gòu)的臨時函數(shù),析構(gòu)將發(fā)生在執(zhí)行內(nèi)聯(lián)匯編指令之后,這與C++析構(gòu)臨時函數(shù)的規(guī)則相類似。
簡單表達(dá)式操作數(shù)包含以下幾種類型。
· 變量值
· 變量地址
· 指針變量的反引用(the dereferencing of a point varable)
· 偽操作指定的程序常量
非簡單表達(dá)式操作數(shù)包含以下幾種類型。
· 隱式函數(shù)調(diào)用,如除法,或顯式函數(shù)調(diào)用
· C++臨時函數(shù)的構(gòu)造
· 算術(shù)或邏輯操作
?。?)寄存器列表
寄存器列表最多可包含 16 個操作數(shù)。這些操作數(shù)可以是虛擬寄存器或表達(dá)式操作數(shù)。在寄存器列表中指定虛擬寄存器和表達(dá)式操作數(shù)的順序非常重要。寄存器列表中操作數(shù)的讀寫順序是從左到右。第一個操作數(shù)使用最低地址,隨后的操作數(shù)的地址依次在前一地址基礎(chǔ)上增加 4。這一點(diǎn)與LDM 或 STM 指令的普通操作(編號最低的物理寄存器總是存入最低的存儲器地址)是不同的。之所以存在這種區(qū)別是因?yàn)樵趦?nèi)聯(lián)匯編中使用的寄存器被編譯器虛擬化了。
同一個表達(dá)式操作數(shù)或虛擬寄存器可以在寄存器列表中出現(xiàn)多次,重復(fù)使用。
如果表達(dá)式操作數(shù)或虛擬寄存器被指定為指令中的基址寄存器,表達(dá)式或虛擬寄存器的值按照ARM指令尋址方式進(jìn)行更新。更新將覆蓋原表達(dá)式或虛擬寄存器的值。
?。?)中間操作數(shù)(Intermediate operands)
在內(nèi)聯(lián)匯編指令中,可能將C或C++整型常量表達(dá)式用作立即數(shù)處理。用于指定直接移位的常量表達(dá)式的值必須在ARM指令規(guī)定的移位操作數(shù)的范圍內(nèi);用于為存儲器或協(xié)處理器數(shù)據(jù)傳送指令指定直接偏移量的常量表達(dá)式,必須符合ARM體系結(jié)構(gòu)中的內(nèi)存對齊標(biāo)準(zhǔn)。
8.函數(shù)調(diào)用和分支跳轉(zhuǎn)
利用內(nèi)聯(lián)匯編程序的BL和SWI指令可在常規(guī)指令字段后指定3個可選列表。這些指令格式有以下幾種。
SWI{cond} swi_num , { input_param_list }, { output_value_list }, { corrupt_reg_list }
BL{cond} function, { input_param_list }, { output_value_list }, { corrupt_reg_list }
其中,swi_num為SWI調(diào)用的中斷號;function為被調(diào)用函數(shù)名;{input_param_list}為輸入參數(shù)列表;{output_value_list}為輸出參數(shù)列表;{corrupt_reg_list}為被破壞寄存器列表。
注意內(nèi)聯(lián)匯編程序不支持BX、BLX和BXJ指令。不能在任何輸入、輸出或“被破壞的寄存器列表(corrupted register list)”中指定lr、sp或pc寄存器;任何SWI指令或函數(shù)調(diào)用不能更改sp寄存器。
非常好我支持^.^
(1) 100%
不好我反對
(0) 0%