C語言與彙編混合程式設計

2021-06-18 17:36:32 字數 3593 閱讀 1308

c語言是乙個系統級的語言,是乙個可以直接深入到硬體最底層操作的語言。在前邊的部分部落格中,我們提到過,對硬體的操作實際上是對硬體控制器中的暫存器或儲存單元進行操作,而在arm架構中,這些暫存器或儲存單元一般是以記憶體對映的方式進行訪問的。在c語言中,指標剛好提供了訪問任意的記憶體位址的方式,因此從語言上是可以表達的。但實際情況呢?

實際上,在操作底層硬體時,c語言還是有其侷限性。有些硬體位址是沒有位址一說的。比如處理器的暫存器,協處理器和協處理器的暫存器,系統控制器等。這些硬體資源是不可能使用c語言指標來訪問的,這時就只好應用彙編指令了。

所以才有了今天的主題---c語言與組合語言混合程式設計:

對於c語言與組合語言混合程式設計使用的方式主要有如下幾種情況:

1)匯程式設計序中呼叫c語言函式

2)匯程式設計序中使用c語言中定義的全域性變數

3)c語言中呼叫組合語言的函式

4)c語言中使用匯程式設計序中定義的全域性變數

5)c語言中內嵌彙編指令(這種和編譯器有關,不同編譯器有不同的方式,這裡討論gun gcc的內嵌方式,其餘的參考相關)

一.c語言呼叫彙編函式

#檔名:a.s

.globl add

add:

popl %eax;

popl %ebx;

movl (%ebx),%ecx;

addl $1,%ecx;

movl %ecx,(%ebx)

pushl %ebx;

pushl %eax;

ret編寫a.c如下

#includeextern int add(int i);

int main()

運用如下命令進行編譯

arm-linux-gcc a.c a.s –o test

這時會生成乙個test檔案,將其拷貝到arm目標機檔案系統中,在shell下執行就可以了。

例子完了,總結一下c語言呼叫彙編函式有一下幾個要點:

1.c語言中用extern 將用到的函式宣告為外部函式

2.彙編中用.globl或.global將函式的標號宣告為全域性型別

3.彙編函式的寫法遵循atpcs約定

二.組合語言中使用c全域性變數

這個例程在彙編中實現對c語言的某個全域性變數的求平方運算

//檔名:square.c

//說明:求平方,彙編使用c全域性變數

#include int var; //這個變數的值在彙編中修改

extern void asm_square();

int main()

彙編原始碼如下:

@檔名:asm_square.s

@說明:求平方,彙編使用c全域性變數

.text

.globl asm_square

.extern var              @ 可以不做這個申明

asm_square:

ldr r0, =var                 @得到var的位址

ldr r1, [r0]                  @得到var的置

mul r2, r1, r1              @乘法

str r2, [r0]                  @將結果儲存回var

mov pc, lr                 @返回

.ltorg                          @宣告文字池,可省略

.end 

需要說明的是在組合語言中甚至不加任何宣告就使用c程式中的全域性變數,一般建議加上乙個.extern宣告以增強可讀性。

三.內嵌彙編

如果只是在c程式中有一小段需要用組合語言實現的**,那麼單獨寫乙個彙編原始檔並進行相互呼叫就顯得有點麻煩了,這時乙個有用的方式就是內嵌彙編:

asm("指令序列":輸出列表:輸入列表:修改內容);

其中asm是關鍵字,除了指令序列外,其餘的三部分都是可以省略的。但如果省略了中間的部分,相應的冒號不可以省略。指令序列不能省略,但可以為空字串。

內嵌彙編使用舉例如下:

asm(「mov %[result], %[value], ror #1」 : [result]「=r」  (y) : [value]「r」 (x));

作用是將變數x迴圈右移1位並賦值給變數y。有關詳細說明如下:

1)指令序列

在指令序列中可以使用輸出列表和輸入列表中定義的各種符號,如:%[value]和%[result]這些符號在生成指令時將替換成對應的內容。

2)輸出列表和輸入列表

這兩個者都是由逗號隔開的多個部分組成,代表多個輸出運算元。每個部分由乙個方括號包圍的符號名,乙個對運算元使用方式進行限定的字元以及乙個圓括號包圍的c語言變數名組成。在早起的gcc版本中,輸入輸出列表不支援符號名。這是指令中必須以%0,%1的方式使用。不過現在gcc也緊跟公升級卻又不忘向前相容。

輸入輸出列表中的限定符

限定符含義

i         用立即數

j         範圍在-4095~4095內的常數

k         按位取反的立即數

l         去相反數的立即數

l         暫存器r0-r7

m         0~32之間的常數或2的整數次冪

m         記憶體位址

r         暫存器r0-r15

x         任意用途

限定符前什麼都不加的表示唯讀,加=表示只寫,加+表示可讀可寫。其中唯讀的放在輸入列表中,而只寫和可讀可寫的運算元應放在輸入列表中。

3)修改列表

修改列表是一系列逗號隔開的字串,代表在嵌入式彙編中被修改過的內容。可以是乙個暫存器名,表示這個暫存器的值被修改了,可以是cc,表示條件標誌被修改了。編譯器將根據這些資訊在內嵌彙編的前後進行保護處理,以免破壞c程式本身對暫存器的使用。

特殊一點的當數memory,表示要修改記憶體中變數,這是編譯器必須從記憶體載入變數的值而不能用快取在暫存器的值,並且要在這條內嵌彙編語句結束後將變數的值寫回記憶體。例如在linux核心原始碼中經常用下面的內嵌彙編:

asm("":::"memory");    這就是所謂的「記憶體屏障」編譯器必須將它之前的變數寫回記憶體,其後用到的變數必須從記憶體中載入。

另外需要注意的就是使用內嵌彙編,它仍然接受編譯器的優化,甚至有可能被完全去掉。可以是用volatile關鍵字來防止這一點。如:

int temp;

asm volatile(

「mrs %0, cpsr\n\t」

「bic %0, %0, #0x80」\n\t

「msr cpsr_c, %0」

: 「=r」 (temp)

:: 「memorg」);

這段**的作用是禁止中斷,如果要在巨集定義中使用內嵌彙編,則asm和volatile關鍵字可能引起編譯器的警告,這時可用__asm__及_volatile__代替。

在不同的內嵌彙編**中相互跳轉是不允許的,但在同乙個內嵌彙編中可以進行跳轉。這時一般可以會使用區域性標籤。如下:

int main()

C語言和彙編混合程式設計

在arm 的c語言程式中,使用關鍵字 asm 下劃線是兩個 來標識一段彙編指令。彙編可以通過對c程式中的宣告的全域性變數進行地間接訪問 1 使用import偽指令宣告所要呼叫的全域性變數 2 使用ldr指令讀取全域性變數的記憶體位址,通常該全域性變數的記憶體位址存放在程式的資料緩衝池中 3 根據該資...

ARM彙編 C語言 混合程式設計

主函式main 第一次實驗 12月4 這是我見過最坑爹的問題 main這個字段不能使用。在release模式下怎麼編譯都過不去,報錯如下 原因 不能使用main這個識別符號。用main main main1 mai都行,就是main不行。原來在debugrel模式下,使用main僅僅是乙個警告,沒想...

ARM彙編與C混合程式設計

內聯彙編即在c中直接使用彙編語句進行程式設計,使程式可以在c程式中實現c語言不能完成的一些工作,例如,在下面幾種情況中必須使用內聯彙編或嵌入型彙編 程式中使用飽和算術運算 saturating arithmetic 程式需要對協處理器進行操作 在c程式中完成對程式狀態暫存器的操作 asm volat...