一步步编写操作系统 69 汇编语言和c语言共同协作 70
由于有了上一節(jié)的鋪墊,本節(jié)的內(nèi)容相對(duì)較少,這里給大家準(zhǔn)備了兩個(gè)小文件來實(shí)例演示匯編語言和c語言相互調(diào)用。
會(huì)兩種不同語言的人,只是掌握了同一件事物的兩種表達(dá)方式。人在學(xué)習(xí)一種新語言時(shí),潛意識(shí)里是建立了語言符號(hào)與事物形象的映射關(guān)系,比如我們?cè)趯W(xué)習(xí)grape這個(gè)單詞時(shí),我們之所以認(rèn)為它就是我們所認(rèn)知的葡萄,是因?yàn)槲覀冎肋@兩個(gè)名詞都是在描述同一種圓圓的、黑紫色、甜酸的這一水果的形象,如果腦子中不存在這個(gè)形象的話,不光是學(xué)不會(huì)grape這個(gè)英文單詞,就連中文的葡萄也不知道是何意。總之,對(duì)于具體的事物,一定是先有其形象,再有其描述,這樣才能理解該事物,了解了事物的本質(zhì)形象后,無論該事物的名字怎樣變化,我們都能將它們相互轉(zhuǎn)換。
也許有同學(xué)會(huì)問,以上這些所說的目的是什么?各位觀眾稍安勿燥,馬上就要入戲啦^_^。
“匯編語言和C語言可以互相調(diào)用”,這句話并不是如表面陳述的那樣,似乎是兩種語言能直接交流,其實(shí)并不是這樣。c語言和匯編語言完全是不同的東西,它們?cè)趺茨苷J(rèn)識(shí)對(duì)方呢。這就像跟不懂漢語的人說漢語,那人聽了肯定會(huì)暈頭轉(zhuǎn)向的,除非身邊有個(gè)翻譯幫忙轉(zhuǎn)述,這個(gè)翻譯所做的工作實(shí)質(zhì)上是在腦子中找到這種語言所描述的事物形象,然后給出這種事物形象的另一種語言表達(dá),這個(gè)事物形象才是翻譯的核心。這有些類似上面提到的葡萄的例子,在同一種指令集上的各種計(jì)算機(jī)程序語言,最終也要編譯為那些相同的機(jī)器碼,這些機(jī)器碼便是高級(jí)語言的本質(zhì)形象。對(duì)于上面提到的翻譯,在計(jì)算機(jī)世界里,就是編譯器,只不過這個(gè)翻譯是有多個(gè),例如本書所說的c語言編譯器gcc和匯編語言編譯器nasm,它們能在一起配合,是因?yàn)樗鼈兌级畽C(jī)器語言。舉個(gè)例子,就像小明只會(huì)漢語和英語,小紅只會(huì)漢語和法語,若他們之間在交流時(shí),小明說英語,小紅說法語,他倆相互都聽不懂,所以,當(dāng)說英文的小明想跟說法語的小紅借作業(yè)時(shí),他必須用漢語告訴小紅。
編譯器知道高級(jí)語言所描述的事物形象是機(jī)器碼,所以各種編譯在高級(jí)語言方面的交流,本質(zhì)上都是將它們都變成統(tǒng)一的機(jī)器碼后實(shí)現(xiàn)的。
吼吼,一不小心又說多了,不知道我表達(dá)清楚了沒有,反正話題就此結(jié)束啦,小弟這里給各位看官準(zhǔn)備了兩個(gè)小文件:C_with_S_c.c和C_with_S_S.S。大家不用細(xì)看,快速瀏覽一下即可,在代碼后面我還有話說呢
C_with_S_c.c
1 extern void asm_print(char*,int);2 void c_print(char* str) {3 int len=0;4 while(str[len++]);5 asm_print(str, len);6 }C_with_S_S.S
1 section .data2 str: db "asm_print says hello world!", 0xa, 03 ;0xa是換行符,0是手工加上的字符串結(jié)束符\0的ascii碼。4 str_len equ $-str56 section .text7 extern c_print8 global _start9 _start:10 ;;;;;;;;;;;; 調(diào)用c代碼中的函數(shù)c_print ;;;;;;;;;;;11 push str ;傳入?yún)?shù)12 call c_print ;調(diào)用c函數(shù)13 add esp,4 ;回收棧空間1415 ;;;;;;;;;;;;;;;;;;; 退出程序 ;;;;;;;;;;;;;;;;;;;;16 mov eax,1 ;第1號(hào)子功能是exit系統(tǒng)調(diào)用17 int 0x80 ;發(fā)起中斷,通知linux完成請(qǐng)求的功能。1819 global asm_print ;相當(dāng)于asm_print(str,size)20 asm_print:21 push ebp ;備份ebp22 mov ebp,esp23 mov eax,4 ;第4號(hào)子功能是write系統(tǒng)調(diào)用24 mov ebx, 1 ;此項(xiàng)固定為文件描述符1,標(biāo)準(zhǔn)輸出(stdout)指向屏幕25 mov ecx, [ebp+8] ;第1個(gè)參數(shù)26 mov edx, [ebp+12] ;第2個(gè)參數(shù)27 int 0x80 ;發(fā)起中斷,通知linux完成請(qǐng)求的功能。28 pop ebp ;恢復(fù)ebp29 ret代碼C_with_S_c.c中的函數(shù)c_print是被匯編代碼C_with_S_S.S調(diào)用的,在c_print的實(shí)現(xiàn)中,它又調(diào)用匯編代碼中的asm_print。它們的關(guān)系如圖
?
下節(jié)再解釋代碼,先去吃飯了。
接上節(jié),前文請(qǐng)見“一步步編寫操作系統(tǒng) 69 匯編語言和c語言共同協(xié)作”,
本節(jié)是對(duì)前文的代碼說明,代碼再貼過來,C_with_S_c.c如下:
1 extern void asm_print(char*,int);2 void c_print(char* str) {3 int len=0;4 while(str[len++]);5 asm_print(str, len);6 }代碼C_with_S_c.c的第1行是聲明外部函數(shù)asm_print,通知編譯器這個(gè)函數(shù)并不在當(dāng)前文件中定義。我們知道它定義在文件C_with_S_S.S中,但編譯器是不知道的,所以只能在鏈接階段將此函數(shù)重新定位,編排地址。
第2~6行是c_print的實(shí)現(xiàn),在它的內(nèi)部,它又調(diào)用了匯編代碼C_with_S_S.S中的函數(shù)asm_print,經(jīng)過第1行的聲明,我們要給它提供兩個(gè)參數(shù):字符串的起始地址及長度。
特別強(qiáng)調(diào)一下,由于這里并不打算把c標(biāo)準(zhǔn)庫也鏈接進(jìn)來,所以在求字符串長度時(shí),我們不能用string.h中的strlen函數(shù)。也就是說即使include <string.h>將其strlen的聲明加進(jìn)來,沒有strlen實(shí)現(xiàn)本身所在的.o文件也是不行的。函數(shù)聲明的作用是:一方面是告訴編譯器該函數(shù)的參數(shù)所需要的棧空間大小及返回值,這樣編譯器能為其準(zhǔn)備好執(zhí)行環(huán)境,另一方面是如果該函數(shù)是在外部文件中定義的,一定要在鏈接階段時(shí)將其對(duì)應(yīng)的目標(biāo)文件一塊鏈接進(jìn)來。所以這里第3~4行通過while循環(huán)求字符串的長度。字符串結(jié)尾必須是空字符’\0’才行,否則while就是死循環(huán)了。這個(gè)字符串是代碼C_with_S_S.S提供的,我們轉(zhuǎn)過去看看。
1 section .data2 str: db "asm_print says hello world!", 0xa, 03 ;0xa是換行符,0是手工加上的字符串結(jié)束符的ascii碼。4 str_len equ $-str56 section .text7 extern c_print8 global _start9 _start:10 ;;;;;;;;;;;; 調(diào)用c代碼中的函數(shù)c_print ;;;;;;;;;;;11 push str ;傳入?yún)?shù)12 call c_print ;調(diào)用c函數(shù)13 add esp,4 ;回收棧空間1415 ;;;;;;;;;;;;;;;;;;; 退出程序 ;;;;;;;;;;;;;;;;;;;;16 mov eax,1 ;第1號(hào)子功能是exit系統(tǒng)調(diào)用17 int 0x80 ;發(fā)起中斷,通知linux完成請(qǐng)求的功能。1819 global asm_print ;相當(dāng)于asm_print(str,size)20 asm_print:21 push ebp ;備份ebp22 mov ebp,esp23 mov eax,4 ;第4號(hào)子功能是write系統(tǒng)調(diào)用24 mov ebx, 1 ;此項(xiàng)固定為文件描述符1,標(biāo)準(zhǔn)輸出(stdout)指向屏幕25 mov ecx, [ebp+8] ;第1個(gè)參數(shù)26 mov edx, [ebp+12] ;第2個(gè)參數(shù)27 int 0x80 ;發(fā)起中斷,通知linux完成請(qǐng)求的功能。28 pop ebp ;恢復(fù)ebp29 ret在代碼C_with_S_S.S的第2行,定義待打印的字符串時(shí),在結(jié)尾人為的加了個(gè)0,它就是空字符’\0’的ascii碼。
第8~9行是將_start導(dǎo)出為全局符號(hào),為的是給鏈接器用的,之前解釋過了。
為了在匯編文件中引用外部的函數(shù)(未必是c代碼中的),需要用extern來聲明所需要的函數(shù)名。
由于我們用到了外部的函數(shù)c_print,所以在第7行用extern c_print來聲明c_print函數(shù)是外部的。第11~13行是為外部函數(shù)c_print壓棧傳參,調(diào)用它后清理?xiàng)?臻g。
第16~17行是調(diào)用exit系統(tǒng)調(diào)用告訴linux我要正常退出。
在匯編語言中導(dǎo)出符號(hào)名是用global關(guān)鍵字,這在之前說_start時(shí)大伙已有所耳聞,global是將符號(hào)導(dǎo)出為全局屬性,對(duì)程序中的所有文件可見,這樣其它外部文件中也可以引用被global導(dǎo)出的符號(hào)啦,無論該符號(hào)是函數(shù)還是變量。
由于在c代碼文件C_with_S_c.c中也調(diào)用了這里的asm_print函數(shù),所以為了讓外部代碼可以引用asm_print。我們?cè)诘?9行用global asm_print將其導(dǎo)出。
第20行之后是asm_print的實(shí)現(xiàn),相信大家已經(jīng)非常明白了,不解釋。
好啦,通過這兩個(gè)例子我相信大伙兒已經(jīng)掌握c和匯編混合編程的方法啦,確切說是方法之一。對(duì)于這種匯編代碼和C代碼單獨(dú)編譯的方式還是較容易的,以后咱們會(huì)講C內(nèi)聯(lián)匯編的方式難為難為大家的,玩笑玩笑,只要用心沒有學(xué)不會(huì)的(聽上去似乎覺得更難了^_^,沒事沒事)。
有關(guān)混合編程的部分就說完了,總結(jié)一下:
- 在匯編代碼中導(dǎo)出符號(hào)供外部引用是用關(guān)鍵字global,引用外部文件的符號(hào)是用關(guān)鍵字extern。
- 在C代碼中只要將符號(hào)定義為全局便可以被外部引用(一般情況下無須用額外關(guān)鍵字修飾,具體請(qǐng)參考c語言手冊(cè)),引用外部符號(hào)時(shí)用extern聲明即可。
好了,大爺再來玩哦。
總結(jié)
以上是生活随笔為你收集整理的一步步编写操作系统 69 汇编语言和c语言共同协作 70的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 浦发淘票票信用卡申请进度查询
- 下一篇: 上半年我国进口原油2.69亿吨,同比增3