1. 簡介
當需要C/C++與彙編混合編程時,可以有以下兩種處理策略:
- 若彙編代碼較短,則可在C/C++源檔案中直接內嵌組合語言實現混合編程。
- 若彙編代碼較長,可以單獨寫成彙編檔案,最後以彙編檔案的形式加入項目中,通過ATPCS規定與C程式相互調用及訪問。
2. 內嵌組合語言指令
用C/C++程式嵌入組譯工具中可以實現一些進階語言沒有的功能,提高程式執行效率。armcc編譯器的內嵌彙編器支援ARM指令集,tcc編譯器的內嵌彙編器支援Thumb指令集。
2.1 內嵌彙編指令的文法格式
在ARM的C語言程式中可以使用關鍵字__asm來加入一段組合語言的程式,格式如下:
__asm{ 指令 [;指令] /* comments */ ... 指令}
其中,{ }中的指令都為彙編指令,一行允許寫多條彙編指令語句,指令語句之間要用分號隔開。在彙編指令段中,備註陳述式採用C語言的注釋格式。ARM C++程式中除了可以使用關鍵字__asm來標識一段內嵌彙編指令程式外,還可以使用關鍵詞asm來表示一段內嵌彙編指令,格式如下:
asm ("指令");
其中,asm後面的括弧中必須是一條彙編指令語句,並且不能包含備註陳述式。
2.2 使能/禁止IRQ中斷執行個體
void enable_IRQ(void) //使能中斷程式{ int tmp; //定義臨時變數,後面使用 __asm //內嵌組譯工具的關鍵詞 { MRS tmp, CPSR //把狀態寄存器載入給tmp BIC tmp, tmp, #80 //將IRQ控制位清0 MSR CPSR_c, tmp //載入程式狀態寄存器 }}void disable_IRQ(void) //禁止中斷程式{ int tmp; //定義臨時變數,後面使用 __asm //內嵌組譯工具的關鍵詞 { MRS tmp, CPSR //把狀態寄存器載入給tmp ORR tmp, tmp, #80 //將IRQ控制位置1 MSR CPSR_c, tmp //載入程式狀態寄存器 }}
2.3 內嵌彙編注意事項
尾碼.S檔案中的彙編指令是用armasm彙編器進行彙編的,而C語言程式中的內嵌彙編指令則是用內嵌彙編器進行彙編的。這兩種彙編器存在一定的差異,所以在內嵌彙編時要注意以下幾點:
2.3.1 小心使用物理寄存器
必須小心使用物理寄存器,如R0~R3、IP(R12)、LR(R14)和CPSR中的N、Z、C、V標誌位。因為計算彙編代碼中的C運算式時,可能使用這些物理寄存器,並會修改N、Z、C、V標誌位。
如計算y=x+x/y;
__asm{ MOV R0, x //把x的值給R0 ADD y, R0, x/y //計算x/y時R0的值會被修改}
2.3.2 內嵌組譯工具中允許使用C變數
在計算x/y時R0會被修改,從而影響R0+x/y的結果。內嵌組譯工具中允許使用C變數,用C變數來代替寄存器R0可以解決上述問題。這時內嵌彙編器將會為變數var分配合適的儲存單元,從而避免衝突的發生。如果內嵌彙編器不能分配合適的儲存單元,它將會報告錯誤。
int var;__asm{ MOV var, x //把x的值給R0 ADD y, var, x/y //計算x/y時R0的值會被修改}
2.3.3 不需要儲存和恢複用到的寄存器
對於在內嵌組合語言程式中用到的寄存器,編譯器在編譯時間會自動儲存和恢複這些寄存器,使用者不用儲存和恢複這些寄存器。除了CPSR和SPSR寄存器外,其他物理寄存器在讀之前必須先賦值,否則編譯器會報錯。
int fun (int x){ __asm { STMFD SP!, {R0} //儲存R0,先讀後寫,彙編出錯 ADD R0, x, #1 EOR x, R0, x LDMFD SP!, {R0} //多餘的 } return x;}
3. 彙編與C/C++程式的變數相互訪問3.1 組譯工具訪問C/C++程式變數
在C/C++程式中聲明的全域變數可以被組譯工具通過地址間接訪問。具體存取方法/步驟如下:
1) 在C/C++程式中聲明全域變數。
2) 在組譯工具中使用IMPORT/EXTERN偽指令聲明引用該全域變數。
3) 使用LDR偽指令讀取該全域變數的記憶體位址。
4) 根據該資料的類型,使用相應的LDR指令讀取該全域變數;使用相應的STR指令儲存該全域變數的值。對於不同類型的變數,需要採用不同選項的LDR和STR指令,如下表所示。
C/C++語言中的變數類型
|
帶尾碼的LDR和STR指令
|
描述
|
unsigned char
|
LDRB/STRB
|
無符號字元型
|
unsigned short
|
LDRH/STRH
|
無符號短整型
|
unsigned int
|
LDR/STR
|
無符號整型
|
char
|
LDRSB/STRSB
|
字元型(8位)
|
short
|
LDRSH/STRSH
|
短整型(16位)
|
對於結構,如果知道各個資料項目的位移量,可以通過儲存/載入指令訪問。如果結構所佔空間小於8個字,可以使用LDM和STM一次性讀寫。
讀取C的一個全域變數,並進行修改,然後儲存新的值到全域變數中:
AREA Example4, CODE, READONLY EXPORT AsmAdd IMPORT g_cVal @聲明外部變數g_cVal,在C中定義的全域變數AsmAdd LDR R1, =g_cVal @裝載變數地址 LDR R0, [R1] @從地址中讀取資料到R0 ADD R0, R0, #1 @加1操作 STR R0, [R1] @儲存變數值 MOV PC, LR @程式返回 END
3.2 C/C++程式訪問組譯工具資料
在組譯工具中聲明的資料可以被C/C++程式所訪問。具體存取方法/步驟如下:
1) 在組譯工具中用EXPORT/GLOBAL偽指令聲明該符號為全域標號,可以被其他檔案應用。
2) C/C++程式中定義相應資料類型的指標變數。
3) 對該指標變數賦值為組譯工具中的全域標號,利用該指標訪問組譯工具中的資料。
假設在組譯工具中定義了一塊記憶體地區,並儲存一串字元,彙編代碼如下:
EXPORT Message @聲明全域標號Message DCB "HELLO$" @定義了5個有效字元,$為結束符
extern char* Message;int MessageLength(){ int Length = 0; char *pMessage; //定義字元指標變數 pMessage = Message; //指標指向Message 記憶體塊的首地址 /*while迴圈,統計字串的長度*/ while(*pMessage != '$') //$為字串的結束符 { Length++; pMessage++; } return(Length); //返回字串的長度}
4. 彙編與C/C++程式的函數相互調用
C/C++程式和ARM組譯工具之間相互調用必須遵守ATPCS(ARM/Thumb Procedure Call Standard)規則。使用ADS的C語言編譯器編譯的C語言子程式會自動滿足使用者指定的ATPCS類型。而對於組合語言來說,完全要依賴使用者來保證各個子程式滿足選定的ATPCS類型。具體來說,組譯工具必須滿足以下3個條件才能實現與C語言的相互調用。
1) 在子程式編寫時必須遵守相應的ATPCS規則。
2) 堆棧的使用要遵守相應的ATPCS規則。
3) 在彙編編譯器中使用-atpcs選項。
4.1 ATPCS基本規則
ATPCS基本規則見ATPCS。
4.2 C程式調用組譯工具
組譯工具的設定要遵循ATPCS規則,保證程式調用時參數的正確傳遞,在這種情況下,C程式可以調用彙編子函數。C程式調用組譯工具的方法如下:
1) 組譯工具中使用EXPORT偽指令聲明本子程式可外部使用,使其他程式可調用該子程式。
2) 在C語言程式中使用extern關鍵字聲明外部函數(聲明要調用的彙編子程式),才可調用此彙編的子程式。
#include <stdio.h>extern void strcopy(char *d, const char *s); //聲明外部函數,即要調用的彙編子程式int main(void){ const char *srcstr = "First ource"; //定義字串常量 char dststr[] = "Second string-destination"; //定義字串變數 printf("Before copying: \n"); printf("src=%s, dst=%s\n", srcstr, dststr); //顯示源字串和目標字串的內容 strcopy(dststr, srcstr); //調用彙編子程式R0=dststr, R1=srcstr printf("After copying: \n"); printf("src=%s, dst=%s\n", srcstr, dststr); //顯示複製後的結果 return(0);}
strcopy實現代碼如下:
AREA Example, CODE, READONLY @聲明程式碼片段Example EXPORT strcopy @聲明strcopy,以便外部函數調用strcopy @ R0為目標字串的地址, R1為源字串的地址 LDRB R2, [R1], #1 @讀取位元組資料,源地址加1 STRB R2, [R0], #1 @儲存讀取的1位元組資料,目標地址加1 CMP R2, #0 @判斷字元是否複製完畢 BNE strcopy @沒有複製完,繼續迴圈複製 MOV PC, LR
4.3 組譯工具調用C程式
組譯工具設定要遵循APTCS規則,保證程式調用時參數的正確傳遞。組譯工具調用C程式的方法如下:
1) 在組譯工具中使用IMPORT偽指令聲明將要調用的C程式函數。
2) 在調用C程式時,要正確設定入口參數,然後使用BL指令調用。
int sum(int a, int b, int c, int d, int e){ return(a+b+c+d+e); //返回5個變數的和}
AREA Example, CODE, READONLY IMPORT sum @ 聲明外部標號sum,即C函數sum() EXPORT CALLSUMCALLSUM STMFD SP!, {LR} @LR寄存器入棧 MOV R0, #1 @設定sum函數入口參數,R0為參數a MOV R1, #2 @R1為參數b MOV R2, #3 @R2為參數c MOV R3, #5 @參數 e=5,儲存到堆棧中 STR R3, {SP, #-4}! MOV R3, #4 @R3為參數d, d=4 BL sum @調用C程式中的sum函數,結果放在R0中 ADD SP, SP, #4 @調整堆棧指標 LDMFD SP, {PC} @程式返回 END
以上程式使用了5個參數,分別使用寄存器R0儲存第1個參數,R1儲存第2個參數,R2儲存第3個參數,R3儲存第4個參數,第5個參數利用堆棧傳送。由於利用了堆棧傳遞參數,在程式調用結束後要調整堆棧指標。組譯工具中調用了C程式的sum子函數,實現了1+2+3+4+5,最後相加結果儲存在R0寄存器中。