內聯匯編和嵌入型匯編的使用
大小:0.4 MB 人氣: 2017-10-19 需要積分:1
標簽:內聯匯編(6450)
??內聯匯編和嵌入型匯編是包含在C‘ target=’_blank‘ style=’cursor:pointer;color:#D05C38;text-decoration:underline;‘》C/C++編譯器中的匯編器。使用它可以在C/C++程序中實現C/C++語言不能完成的一些工作。例如,在下面幾種情況中必須使用內聯匯編或嵌入型匯編。· 程序中使用飽和算術運算(Saturating arithmetic),如SSAT16 和 USAT16指令。
· 程序中需要對協處理器進行操作。
· 在C或C++程序中完成對程序狀態寄存器的操作。
使用內聯匯編編寫的程序代碼效率也比較高。
12.1.1 內聯匯編
1.內聯匯編語法
內聯匯編使用“_asm”(C++)和“asm”(C和C++)關鍵字聲明,語法格式如下所示。
· __asm(“instruction[;instruction]”); // 必須為單條指令
__asm{instruction[;instruction]}
· __asm{
。..
instruction
。..
}
· asm(“instruction[;instruction]”); // 必須為單條指令
asm{instruction[;instruction]}
· asm{
。..
instruction
。..
}
內聯匯編支持大部分的ARM指令,但不支持帶狀態轉移的跳轉指令,如BX和BLX指令,詳見ARM相關文檔。
由于內聯匯編嵌入在C或C++程序中,所有在用法上有其自身的一些特點。
① 如果同一行中包含多條指令,則用分號隔開。
② 如果一條指令不能在一行中完成,使用反斜杠“/”將其連接。
③ 內聯匯編中的注釋語句可以使用C或C++風格的。
④ 匯編語言中使用逗號“,”作為指令操作數的分隔符,所以如果在C語言中使用逗號必須用圓括號括起來。如,__asm {ADD x, y, (f(), z)}。
⑤ 內聯匯編語言中的寄存器名被編譯器視為C或C++語言中的變量,所以內聯匯編中出現的寄存器名不一定和同名的物理寄存器相對應。這些寄存器名在使用前必須聲明,否則編譯器將提示警告信息。
⑥ 內聯匯編中的寄存器(除程序狀態寄存器CPSR和SPSR外)在讀取前必須先賦值,否則編譯器將產生錯誤信息。下面的例子顯示了內聯匯編和真正匯編的區別。
錯誤的內聯匯編函數如下所示。
int f(int x)
{
__asm
{
STMFD sp!, {r0} // 保存r0不合法,因為在讀之前沒有對寄存器寫操作
ADD r0, x, 1
EOR x, r0, x
LDMFD sp!, {r0} // 不需要恢復寄存器
}
return x;
}
將其進行改寫,使它符合內聯匯編的語法規則。
int f(int x)
{
int r0;
__asm
{
ADD r0, x, 1
EOR x, r0, x
}
return x;
}
下面通過幾個例子進一步了解內聯匯編的語法。
① 字符串拷貝
下面的例子使用一個循環完成了字符串的拷貝工作。
#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;
}
② 中斷使能
下面的例子通過讀取程序狀態寄存器CPSR并設置它的中斷使能位bit[7]來禁止/打開中斷。需要注意的是,該例只能運行在系統模式下,因為用戶模式是無權修改程序狀態寄存器的。
__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();
}
③ 分隔符的計算
下面的例子計算兩個整數數組中分隔符“,”的個數。該例子顯示了如何在內聯匯編中訪問C或C++語言中的數據類型。該例中的內聯匯編函數mlal()被編譯器優化為一條SMLAL指令,可以使用-S –interleave編譯選項使編譯器輸出匯編結果。
#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.內聯匯編中的限制
可以在內聯匯編代碼中執行的操作有許多限制。這些限制提供安全的方法,并確保在匯編代碼中不違反 C 和 C++ 代碼編譯中的假設。
① 不能直接向程序計數器PC賦值。
② 內聯匯編不支持標號變量。
③ 不能在程序中使用“。”或{PC}得到當前指令地址值。
④ 在16進制常量前加“0x”代替“&”。
⑤ 建議不要對堆棧進行操作。
⑥ 編譯器可能會使用r12和r13寄存器存放編譯的中間結果,在計算表達式值時可能會將寄存器r0~r3、r12及r14用于子程序調用。另外在內聯匯編中設置程序狀態寄存器CPSR中的標志位NZCV時,要特別小心,內聯匯編中的設置很可能會和編譯器計算的表達式的結果沖突。
⑦ 用內聯匯編代碼更改處理器模式是可能的。然而,更改處理器模式會禁止使用 C或 C++操作數或禁止對已編譯C或C++代碼的調用,直到將處理器模式更改回原設置之后之前的函數庫才可正常使用。
⑧ 為Thumb狀態編譯C或C++時,內聯匯編程序不可用且不匯編Thumb指令。
⑨ 盡管可以使用通用協處理器指令指定 VFP 或 FPA 指令,但內聯匯編程序不為它們提供直接支持。
不能用內聯匯編代碼更改 VFP 向量模式。內聯匯編可包含浮點表達式操作數,該操作數可使用編譯程序生成的 VFP 代碼求出操作數值。因此,僅由編譯程序修改 VFP 狀態很重要。
⑩ 內嵌匯編不支持的指令有BX、BLX、BXJ和BKPT指令。而LDM、STM、LDRD和STRD指令可能被等效為ARM LDR或STR指令。
3.內聯匯編中的虛擬寄存器
內聯匯編程序提供對 ARM 處理器物理寄存器的非直接訪問。如果在內聯匯編程序指令中將某個ARM寄存器用作操作數,它就成為相同名稱的虛擬寄存器的引用,而不是對實際物理ARM寄存器的引用。例如內聯匯編指令中使用了寄存器r0,但對于C編譯器,指令中出現的r0只是一個變量,并非實際的物理寄存器r0,當程序運行時,可能是由物理寄存器r1來存放r0所代表的值。
下面的例子顯示了編譯器如何對內聯匯編指令的寄存器進行分配。
程序的源代碼如下。
#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編譯出的代碼,使用其他編譯器結果可能有差異。同一段內嵌匯編經過不同版本的編譯器編譯后,在指令里可能使用不一樣的實際寄存器,但是只要遵循文檔里的編碼指導,執行的功能肯定相同。
例子中以“》》》”的開頭的行是程序的源碼部分,緊接其后的是由編譯器編譯出的匯編代碼。從上例可以很清楚地看出,源程序中使用了r5、r6和r7,但由編譯器編譯后的代碼使用了寄存器r1、r2和r3。
另外,需要特別指出的是在內聯匯編中使用寄存器必須先聲明其變量類型,如上例中的“int r5,r6,r7”。如果不在使用前進行聲明,編譯器將給出以下錯誤信息。
#1267-D: Implicit physical register R3 should be defined as a variable
編譯程序定義的虛擬寄存器有函數局部作用范圍,即在同一個C函數中,涉及相同虛擬寄存器名稱的多個asm語句或聲明,訪問相同的虛擬寄存器。
內聯匯編沒有為pc(r15)、lr(r14)和sp(r13)寄存器創建虛擬寄存器,而且不能在內聯匯編代碼中讀取或直接修改它們的值。如果內聯匯編程序中出現了對這些寄存器的訪問,編譯器將給出以下錯誤消息。例如,如果指定r14:
#20: identifier “r14” is undefined
內聯匯編可以直接使用CPSR和SPSR對程序狀態字進行操作,因為內聯匯編中不存在虛擬處理器狀態寄存器(PSR)。任何對 PSR 的引用總是指向物理 PSR。
4.內聯匯編中的指令展開
內聯匯編代碼中的ARM指令可能會在編譯過程中擴展為幾條指令。擴展取決于指令、指令中指定的操作數個數以及每個操作數的類型和值。通常,被擴展的指令有以下兩種情況:
· 含有常數操作的指令;
· LDM、STM、LDRD 和 STRD指令;
· 乘法指令MUL被擴展為一系列的加法和移位指令。
下面的例子說明了編譯器如何對含有常數操作的指令進行擴展。
包含有常數操作的加法指令:
ADD r0,r0,#1023
被編譯器編譯為如下兩條指令:
ADD r0,r0,#1024
SUB r0,r0,#1
注意擴展指令對程序狀態寄存器CPSR的影響:算術指令影響相應的NZCV標準位;其他指令設置NZ標志位不影響V標志位。
所有的LDM和STM指令被擴展為等效的LDR和STR指令序列。然而,在優化過程中,編譯程序可能因此將單獨的指令重組為一條LDM或STM指令。
5.內聯匯編中的常數
指令中的標志符“#”是可選的(前面的例子中,指令中常數前均加了標志符“#”)。如果在指令中使用了“#”,則其后的表達式必為常數。
6.內聯匯編指令對標志位的影響
內聯匯編指令可能顯式或隱式地更新處理器程序狀態寄存器的條件標志位。在僅包含虛擬寄存器操作數或簡單表達式操作數的內聯匯編中,其執行結果是可以預見。如果指令中指定了隱式或顯式更新條件標志位,則條件標志位根據指令的執行進行設置。如果未指定更新,則條件標志不會更改。如果內嵌匯編指令的操作數都不是簡單操作數時或指令不顯式更新條件標志位,則條件標志位可能會被破壞。一般情況下,編譯程序不易診斷出對條件標志的潛在破壞。然而,在構造析構C++臨時函數的操作數時,如果指令試圖更新條件標志,編譯程序將給予警告,因為析構函數可能會破壞條件標志位。
7.內聯匯編指令中的操作數
內聯匯編指令中的操作數分為以下4種。
· 虛擬寄存器
· 表達式操作數
· 寄存器列表
· 中間操作數
(1)虛擬寄存器
在內聯匯編指令中指定的寄存器表示虛擬寄存器而不是實際的物理寄存器。由編譯器編譯的匯編代碼中使用的物理寄存器可能與在指令中指定的不同。每個虛擬寄存器的初值是不可預測的,必須在讀取之前將初值寫入虛擬寄存器。如果在寫入之前試圖讀虛擬寄存器,編譯程序會給予警告。
(2)表達式操作數
在內聯匯編指令中,可將函數自變量、C或C++變量和其他C或C++表達式指定為寄存器操作數。用作操作數的表達式必須為整數類型,如char、short、int或long,(長整型long long除外)或指針類型。當表達式作為內聯匯編指令的操作數時,編譯器在編譯時自動增加一段代碼計算表示式的值并將其加載到指定的寄存器中。
注意數據類型中除char和short(默認為無符號類型)外,其他均為有符號類型。
下面的例子顯示了編譯器如何處理內聯匯編中的表達式操作數。
程序源代碼如下所示。
/* 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 ();
}
由編譯器編譯出的匯編代碼如下所示(其中只列出了內聯匯編的一段代碼)。
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”分為兩步來完成,用戶在編寫自己的內聯匯編應用程序時要特別注意這一點。
包含多個表達式操作數的指令,沒有指定表達式操作數求值的順序。
將C或C++表達式用作內聯匯編程序操作數,如果表達式的值不能滿足 ARM指令中所要求的指令操作數約束條件,一條指令將被擴展為多條指令。
如果用作操作數的表達式創建需要析構的臨時函數,析構將發生在執行內聯匯編指令之后,這與C++析構臨時函數的規則相類似。
簡單表達式操作數包含以下幾種類型。
· 變量值
· 變量地址
· 指針變量的反引用(the dereferencing of a point varable)
· 偽操作指定的程序常量
非簡單表達式操作數包含以下幾種類型。
· 隱式函數調用,如除法,或顯式函數調用
· C++臨時函數的構造
· 算術或邏輯操作
(3)寄存器列表
寄存器列表最多可包含 16 個操作數。這些操作數可以是虛擬寄存器或表達式操作數。在寄存器列表中指定虛擬寄存器和表達式操作數的順序非常重要。寄存器列表中操作數的讀寫順序是從左到右。第一個操作數使用最低地址,隨后的操作數的地址依次在前一地址基礎上增加 4。這一點與LDM 或 STM 指令的普通操作(編號最低的物理寄存器總是存入最低的存儲器地址)是不同的。之所以存在這種區別是因為在內聯匯編中使用的寄存器被編譯器虛擬化了。
同一個表達式操作數或虛擬寄存器可以在寄存器列表中出現多次,重復使用。
如果表達式操作數或虛擬寄存器被指定為指令中的基址寄存器,表達式或虛擬寄存器的值按照ARM指令尋址方式進行更新。更新將覆蓋原表達式或虛擬寄存器的值。
(4)中間操作數(Intermediate operands)
在內聯匯編指令中,可能將C或C++整型常量表達式用作立即數處理。用于指定直接移位的常量表達式的值必須在ARM指令規定的移位操作數的范圍內;用于為存儲器或協處理器數據傳送指令指定直接偏移量的常量表達式,必須符合ARM體系結構中的內存對齊標準。
8.函數調用和分支跳轉
利用內聯匯編程序的BL和SWI指令可在常規指令字段后指定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調用的中斷號;function為被調用函數名;{input_param_list}為輸入參數列表;{output_value_list}為輸出參數列表;{corrupt_reg_list}為被破壞寄存器列表。
注意內聯匯編程序不支持BX、BLX和BXJ指令。不能在任何輸入、輸出或“被破壞的寄存器列表(corrupted register list)”中指定lr、sp或pc寄存器;任何SWI指令或函數調用不能更改sp寄存器。
非常好我支持^.^
(1) 100%
不好我反對
(0) 0%
下載地址
內聯匯編和嵌入型匯編的使用下載
相關電子資料下載
- 在Rust中使用內聯匯編 324
- 內聯匯編代碼中的關鍵語法規則講解 2155
- 內聯匯編很可難嗎 看完這篇文章就能搞定! 1910
- 幾種情況中必須使用內聯匯編或嵌入型匯編 2098
- 哪幾種情況中必須使用內聯匯編或嵌入型匯編 583
- 內聯匯編的技巧 761