深入理解C語言(組合語言程式設計師角度)
轉自:http://blog.sina.com.cn/s/blog_16696ec8f0102wq2d.html
一、比較C與彙編的語言要素(可有表格、例子等)
1.資料型別比較:所有資料型別(數與資訊、無符號、有符號等;包括布林型別、 指標、多維陣列、結構、聯合、自定義型別等; 類class(選作可加分)
組合語言:
BYTE |
8位無符號整數 |
1位元組 |
SBYTE |
8位有符號整數 |
1位元組 |
WORD |
16位無符號整數 |
2位元組 |
SWORD |
16位有符號整數 |
2位元組 |
DWORD |
32位無符號整數 |
4位元組 |
SDWORD |
32位有符號整數 |
4位元組 |
FWORD |
48位整數 |
6位元組 |
QWORD |
64位整數 |
8位元組 |
TBYTE |
80位整數 |
10位元組 |
REAL4 |
32位IEEE短實數 |
4位元組 |
REAL8 |
64位IEEE長實數 |
8位元組 |
REAL10 |
80位IEEE擴充套件精度實數 |
10位元組 |
字串 |
通常每個位元組儲存一個字元 |
若干個位元組 |
TYPE |
自定義型別 |
若干個位元組 |
C語言:
short |
短整型 |
2位元組 |
Int |
整型 |
4位元組 |
Long |
長整型 |
8位元組 |
Float |
單精度 |
4位元組 |
Double |
雙精度 |
8位元組 |
Char |
字元型 |
1位元組 |
Array |
陣列 |
|
Struct |
結構體 |
|
Union |
共用體 |
|
Enum |
列舉型別 |
|
Point |
指標型別 |
|
Void |
空型別 |
|
Bool |
|
1位元組 |
綜合比較:C語言與組合語言一般的資料型別都差不多,不過C語言當中沒有對應三位元組或是五位元組的的變數型別,要麼是int型,要麼是long型,所以每次申請必須是固定的位元組數,勢必造成記憶體使用上的浪費,而記憶體使用效率不高同時也會影響到整個程式的整體效率。而大部分的組合語言沒有這樣的語法,在偽指令的幫助下,組合語言程式可以使用任意位元組的變數,當然處理起來比C語言麻煩得多,最終還是一個位元組一個位元組地拼接處理,不過這些最終會由編譯器來完成。
組合語言採用不同的字尾區分:B:二進位制數; O:八進位制數; D:十進位制數; H:十六進位制數;當一個數值後面沒有後綴的時候,預設為十進位制數;字串常數是用一對單引號('')括起 來的一串字元。
另外一個區別就是指標型別了,組合語言的定址方式與C語言中的指標是類似的。組合語言的定址方式與C語言中的指標都是尋找資料的方法。指標就是存了變數的地址,定址方式就是得到儲存變數的地址。
2.常量(包括符號常量)、常量表達式、變數(暫存器是特殊的全域性變數、全域性變數、區域性變數、靜態變數)、變量表達式、條件表示式、關係表示式
|
C語言 |
組合語言 |
常量 |
1. 巨集定義:#define N 3 2. 給變數初始化賦值: Contest int a=1; contest char c=’a’; |
1. AA EQU 256 2. EMP = 5 |
變數 |
Int a=1;char a=’S’;等 |
[變數名] 資料定義偽指令 初始值(可以有多個) 如:count DWORD 100 Arr BYTE 1,2,3,4 |
C語言提供了34種運算子,這其中包括算術運算子,增1,減1運算子以及強制型別轉換運算子等等。例如+、-、*、/、-(取反)、++,--。運算子有優先順序,在組成算術表示式時,編譯器將按照預先預定好的優先順序對錶達式進行運算。C語言還提供了巨集的功能,包括巨集替換和const
組合語言:
算數運算操作符: +、-、*、/、MOD,等;取模運算MOD是取兩數相除的餘數;
邏輯運算操作符: AND(邏輯與)、OR(邏輯或)、NOT(邏輯非)、XOR(邏輯
異或)
注意:邏輯運算子同時又可以是邏輯運算指令的指令碼,只有當它們出現在指
令的運算元部分時,才是操作符;例如:
ADD AL,0CH ADD 0FH ;第一個ADD是指令碼,第二個ADD是操作符;
關係運算操作符: EQ(相等)、NE(不等)、LT(小於)、GT(大於)、LE(小於等於)、GE(大於等於);
組合語言中的表示式不能單獨構成語句,只能是語句的組成部分
3.賦初值(初始化)、賦值語句(全域性、區域性變數)、算術運算(整數、浮點、有無符號)及邏輯運算,分支轉移語句(含多分支Switch)、迴圈語句(3種)
C語言:
(1) C語言的算術運算和表示式:C語言提供了三十多種運算子,其中包括算術運算子,加一,減一運算子以及強制型別轉換運算子等等。例如+ 、-、*、/、-(取反)、++、--。運算子有優先順序在組成算術表示式時,編譯器會按照預先預定好的優先順序對錶達式進行運算。
(2) 選擇控制結構:C語言的選擇控制結構有IF()語句,多選分支的if else語句,switch()語句等
(3) 迴圈控制語句:C語言的迴圈控制語句有for()語句,while()語句,do()while()語句;輔助語句有continue和break語句。
組合語言:
彙編運算由運算元和操作符組成:算術運算操作符由+、-、*、/、MOD(取餘)等;邏輯運算子:AND OR NOT XOR 邏輯運算子同時又可以是邏輯運算指令的指令碼,只有當它們出現在指令的運算元部分時,才是操作符。例如
ADD AL,0CH ADD 0FH;第一個ADD是指令碼,第二個ADD是操作符;
關係運算符:EQ(相等)NE(不相等)LT(小於)GT(大於)LE(小於等於)GE(大於等於)
組合語言中的表示式不能單獨構成語句,只能是語句的組成部分。
語句中表達式的求值不是在語句執行時完成的,而是在對源程式進行彙編連結是完成的,所以,語句中各表示式的值必須在彙編或連結是就是確定的,也就是說,表示式中各識別符號的值在彙編或連結時就應該是確定的。
4.子程式(函式)的宣告、呼叫與返回、引數傳遞、返回值,傳值與傳地址
|
宣告 定義 |
呼叫與返回 |
引數傳遞 |
傳值與傳地址 |
C語言(函式) |
返回值型別 函式名(型別 形參1,型別形參2,… ) {(這裡面為函式體) 宣告語句序列
可執行語句序列 } |
函式(如main函式)呼叫其他函式時,必須提供實參給被呼叫的函式。 如果沒有函式返回值,就用void定義返回值型別; 若有函式返回值,則用相應的返回值型別來定義,有返回值的函式必須要有return語句。 |
主調函式把實參的值複製給被調函式的形參的過程叫引數傳遞;形參是函式的入口,形參表裡的形參就相當於運算的運算元,函式的返回值就相當於運算的結果 |
傳值 |
組合語言(子程式) |
純純模式-標號模式 F:MOV EAX,1 ….. RET PROC/ENDP模式(如MAIN) PROTO/INVOKE模式(如printf等先聲明後使用) |
在主程式裡用CALL P呼叫子程式P,在子程式裡是用RET返回子程式; 呼叫和返回是通過堆疊來實現的:把CALL的下一條指令的地址壓棧 |
1. 暫存器傳遞引數:佔用暫存器,主程式要對引數暫存器賦值,雙方都知道約定的暫存器是哪幾個;速度最快;比較簡潔 2. 變數傳遞引數:雙方都知道引數是哪些全域性變數;主程式,子程式都簡潔 3. 堆疊傳遞函式:堆疊框架的使用 |
傳地址 可以在段內呼叫,也可以跨段呼叫 |
5.偽指令與巨集:巨集宣告、呼叫與返回,引數傳遞。
組合語言:
偽指令:
偽指令是內嵌在源程式程式碼中,由彙編器識別並執行相應動作的命令,偽指令在程式執行中並不執行,偽指令可以用來定義變數,巨集及過程。常用的偽指令有:mov .data .code .stack invoke jmp call等等
巨集:
巨集是具有巨集名的一段組合語言序列,巨集是組合語言的一個特點,它是與子程式類似又獨具特色的另一種化簡源程式的方法。
巨集的宣告:巨集可以直接在程式的頭部(開始)定義,或者也可以放在單獨的文字檔案中,通過INCLUDE偽指令把巨集定義複製(插入到)源程式中。
巨集的定義: 巨集名 macro [形參表]
巨集定義體
endm
例如:
mainbegin MACRO ;;定義名為mainbegin的巨集,無引數
mov AX,1;;巨集定義體
mov AH, 4ch
ENDM ;;巨集定義結束
mained MACRO retnum ;;帶有形參retnum
mov AL,retnum ;;巨集定義中實用引數
mov AH,4ch
int 21h
ENDM
巨集的呼叫:
start: mainbegin ;巨集呼叫,建立DS內容
mainend 0;巨集呼叫
end start
巨集呼叫的實質是在彙編過程中進行巨集展開
巨集展開的具體過程是:巨集是在彙編器的預處理階段展開的。當彙編程式掃描源程式遇到已有定義的巨集呼叫時,即用相應的巨集定義體來取代源程式的巨集指令,同時用位置匹配的實參對形參進行取代。
巨集展開:在彙編時,用巨集定義體的程式碼序列代替巨集指令的過程。
巨集的引數:
巨集定義時,可以無引數,例如mainbegin 可以帶有一個引數,例如mained
也可以有多個引數;
引數可以是常數,變數,儲存單元,指令(操作碼)或它們的一部分,也可以是表示式。巨集的引數沒有型別,因此前處理器並不檢查實參的型別是否與形參的型別匹配。如果實際上不匹配,那麼這類錯誤在巨集展開後由彙編器檢查。
巨集引數傳遞:呼叫巨集時的每個實際引數都是都是一個文字值,該值將替換巨集定義中的形參,實際引數的順序必須與形參的定義順序相同。當實參的個數少於形參的個數時,那麼未傳遞的引數為空,如果實參的個數多餘形參的個數時,彙編器會產生警告。
巨集定義體可以是任何合法的彙編語句,既可以是硬指令序列,又可以是偽指令序列。
與巨集有關的偽指令有:LOCAL(區域性識別符號列表),PURGE 巨集名錶(刪除偽指令),EXITM(退出偽指令)
一個巨集的例子:
chr$ MACRO any_text : VARARG; 目前先用, 巨集時講
LOCAL txtname
.DATA
txtname db any_text, 0
ALIGN 4
.CODE
EXITM
ENDM
C語言的偽指令巨集:
巨集常量也稱符號常量,是指用一個識別符號來來表示的常量。巨集常量是由巨集定義編譯預處理命令來定義的。巨集定義的一般形式為: #define 識別符號(巨集名-常字母全部大寫) 字串 其作用是用#define編譯預處理指令定義一個識別符號和一個字串,凡在源程式中發現該識別符號時,都用其後特定的字串來替換。
跟組合語言一樣,巨集替換時不做任何語法檢查。
值得注意的是,巨集名與字串之間可以有多個空白符號,但無需加等號,且字串後只能以換行符終止,一般不以分號結尾。若字串後加分號,則巨集替換時會連同分號一起替換。
6.類:類定義、成員變數與初始化、成員函式、繼承、派生、友元、多型、過載、公共/私有,構造/析構;容器、模板。(選做,可加分)
7.向外部提供變數或子程式 ,使用外部變數和子程式
C語言調用匯編語言的變數和子程式通過 extern ‘C’+呼叫的內容,例如:
C語言為組合語言提供的變數和方法放在 extern ‘C’{}中進行實現,例如:
組合語言呼叫C語言的變數和方法通過extern C PROTO C:ptr 例如:
組合語言為C語言提供的變數和子程式在.data中 例如:
8.標頭檔案:.inc 與.h的內容區別,格式區別
一般用.h .inc很少用,二者沒什麼差別 比如include\ 的庫函式標頭檔案定義就都是.h 補充:具體來說: *.inc 檔案 多是存放配置檔案的。 *.h 檔案 多是變數宣告以及函式的宣告
9.源程式模板:主要區別(包括註釋)
組合語言源程式模板:
TITLE Program Template (Template)
;程式的描述:
;作者:
;建立日期:
;修改:
;日期: 修改者:
INCLUDE Irvine32.INC ;標頭檔案
.DATA
;(在此插入變數)
.CODE
Mian PROC
;(在此插入可執行程式碼)
exit
main ENDP
;(在此插入其他子程式)
END main
C語言源程式模板:
#include
int main()
{
return o;
}
10. 第一條語句標識的區別
main(C語言) |
main PROC(組合語言) |
一、main()函式的形式在最新的 C99 標準中,只有以下兩種定義方式是正確的: 二、main()函式的返回值從前面我們知道main()函式的返回值型別是int型的,而程式最後的 return 0; 正與之遙相呼應,0就是main()函式的返回值。那麼這個0返回到那裡呢?返回給作業系統,表示程式正常退出。因為return語句通常寫在程式的最後,不管返回什麼值,只要到達這一步,說明程式已經執行完畢。而return的作用不僅在於返回一個值,還在於結束函式。 三、main()函式的引數C編譯器允許main()函式沒有引數,或者有兩個引數(有些實現允許更多的引數,但這只是對標準的擴充套件)。這兩個引數,一個是int型別,一個是字串型別。第一個引數是命令列中的字串數。按照慣例(但不是必須的),這個int引數被稱為argc(argument count)。第二個引數是一個指向字串的指標陣列。命令列中的每個字串被儲存到記憶體中,並且分配一個指標指向它。按照慣例,這個指標陣列被稱為argv(argument value)。系統使用空格把各個字串格開。一般情況下,把程式本身的名字賦值給argv[0],接著,把最後的第一個字串賦給argv[1],等等。 |
PROC 偽指令用來標識一個過程的開始,過程的名字是main(main過程是彙編程式的主過程) 該過程又被稱之為啟動過程,以exit語句結束 |
二:自編C程式,對其執行檔案反彙編,深入分析並闡述C語言各要素的底層實現(提供C、ASM、記憶體、暫存器等截圖)--以32位編譯器為例
C語言源程式為:
#include
#include
#include
//呼叫外部程式的東東在這兒宣告
extern "C" char *vasm; //傳過來的陣列必須是地址,而不是陣列的內容
extern "C" int aaaa;
extern "C" int fasm(char *a, int d);
//為外部程式提供的東東在這兒宣告
extern "C"
{
char *vc = "C的變數歡迎你";
int cccc = 0xcccc;
void fc(char *s, int d);
}
void fc(char *s, int d)
{
printf("====現在是C語言的程式碼在執行====\n");
printf(" 顯示引數的內容: %s X \n", s, d);
printf("==== C 語言程式現在執行完畢====\n");
}
typedef struct sample
{
char m1;
int m2;
char m3;
}SAMPLE;
int add(int a, int b) //計算兩個整數的和
{
return (a+b);
}
int main()
{
int sum;
fc(vc, cccc);
fc(vasm, aaaa);
fasm(vasm, aaaa);
fasm(vc, cccc);
int a[5], *p;
printf("Input five numbers:");
for (p = a; p < a + 5; p++)
{
scanf_s("%d", p);
}
for (p = a; p < a + 5; p++)
{
printf("M", *p);
}
printf("\n");
//system("pause");
SAMPLE s = { 'a',2,'b' };
printf("byte=%d\n", sizeof(s));//計算結構體所佔記憶體位元組數
sum = add(2, 3);
printf("sum=%d\n", sum);
getchar();
return 0;
}
C語言呼叫的組合語言的源程式為:
.586
.MODEL flat, STDCALL
.STACK 4096
option casemap : none; 大小寫敏感
includelib "E:\vs2015\project\msvcrt.lib"
; 為外部提供的東東在這兒宣告
public C vasm, aaaa, fasm
; 外部的變數在這兒宣告
extern C vc : ptr BYTE, cccc : DWORD
; 函式不論內外都在這兒宣告
printf PROTO C : ptr byte, : vararg
fc PROTO C : ptr BYTE, : DWORD
fasm PROTO C : ptr BYTE, : DWORD
chr$ MACRO any_text : vararg
LOCAL textname
.const
textname db any_text, 0
ALIGN 4
.code
EXITM <</span>OFFSET textname>
ENDM
.data
vasm DWORD offset casm; !!!!!為外部提供的陣列要這麼宣告!!!!!
casm BYTE "彙編的變數歡迎你!"; !!!casm作為共享變數傳出去是其內容,而不是地址!!!
aaaa DWORD 0aaaah
.code
fasm proc C uses ebx, s:ptr BYTE, d : DWORD
invoke printf, chr$(0dh, 0ah, "====現在執行的是組合語言程式====", 0dh, 0ah)
invoke printf, chr$(" 顯示外部C變數的內容: %s X ", 0dh, 0ah), vc, cccc
invoke printf, chr$(" 顯示引數的內容: %s X ", 0dh, 0ah), s, d
invoke printf, chr$(" !彙編中開始呼叫C的函式!", 0dh, 0ah)
invoke fc, s, d
invoke printf, chr$("====組合語言程式現在執行完畢====", 0dh, 0ah, 0dh, 0ah)
ret
fasm endp
END
11.對於程式
int main(int argc,char *argv[])
{
for (int i = 0; i < argc; i++)
{
printf("第個引數為:%s\n",i,argv[i]);
}
getchar();
return argc++;
}
windows下的可執行程式是PE結構,PE結構按照作業系統的載入規範填充,由作業系統的載入器調入記憶體,分配程序ID執行,跟main函式無關。main函式只是在編譯時,由編譯器識別,編譯成可執行檔案,main函式跟可執行程式無關,知識個標識
argc是命令列總的引數個數
argv[]是argc個引數,其中第0個引數是程式的全名,以後的引數
命令列後面跟的使用者輸入的引數
在 C89 中,main( ) 是可以接受的。Brian W. Kernighan 和 Dennis M. Ritchie 的經典鉅著 The C
programming Language 2e(《C 程式設計語言第二版》)用的就是 main( )。不過在最新的 C99 標準中,只有以下兩種定義方式是正確的:
int main( void )
int main( int argc, char *argv[] )
當然,我們也可以做一點小小的改動。例如:char *argv[] 可以寫成 char **argv;argv 和 argc 可以改成別的變數名(如 intval 和 charval),不過一定要符合變數的命名規則。
如果不需要從命令列中獲取引數,請用int main(void) ;否則請用int main( int argc, char *argv[] )。
main 函式的返回值型別必須是 int ,這樣返回值才能傳遞給程式的啟用者(如作業系統)。
如果 main 函式的最後沒有寫 return 語句的話,C99 規定編譯器要自動在生成的目標檔案中(如 exe 檔案)加入return 0; ,表示程式正常退出。不過,最好在main函式的最後加上return 語句。
三、比較C與彙編的優缺點,適應場合
C語言屬於高階語言,具有可移植性,能夠結構化程式設計。使用標準C語言的程式,幾乎都可以不作改變移植到不同的微機平臺上,對於嵌入式等的微控制晶片,屬於標準C語言的部分也很少需要修改,而且程式很容易讀懂。
C語言編寫程式結構清晰,移植性好,容易維護和修改。
C語言對作業系統和系統使用程式以及需要對硬體進行操作的場合,用C語言明顯優於其它高階語言,許多大型應用軟體都是用C語言編寫的。 C語言具有繪圖能力強,可移植性,並具備很強的資料處理能力,因此適於編寫系統軟體,三維,二維圖形和動畫它是數值計計算的高階語言。
組合語言針對不同的作業系統平臺,不同的微控制器,指令都是完全不同的,即使指令相似,也不具有可移植性。但是組合語言是針對專門的控制器的,所以執行速度可以精確到一個指令週期。組合語言的程式讀懂需要藉助微控制器的指令手冊以及各個暫存器的說明,所以很難讀懂。 組合語言編寫程式碼實時性強,能夠直接控制硬體的工作狀態,但是不具有可移植性,維護和修改困難。
彙編的應用主要是微控制器和微機程式,還有一些計算機外部裝置的驅動程式,主要是一些要求程式執行效率的場合,以及時間要求精確的場合,主要都是用匯編。
四、總結與分析,頓悟與暢想
這次的彙編大作業是一個挺大的專案了。在完成的過程中,我通過查詢課本,PPT,網上的資料加上跟同學討論最終算是基本上完成了題目的要求。不過還是有一些要求沒有完成。我感覺在這個過程當中查詢資料的能力很重要,一個人的知識畢竟是有限的,而只有能夠熟練和快速地通過不同的渠道找到自己需要的資料,這對我以後的學習和研究也是影響很大的。另外,通過這次的作業,我發現自己對組合語言和C語言基本知識的掌握還存在一些問題,而有些問題在平常中是很難遇到的,這為我在接下來的幾天複習彙編的時間裡指明瞭重點和方向。通過對組合語言和C語言的比較能更加清晰地掌握和理解這兩門語言。這也啟示我在以後的學習中要更多地通過聯絡和比較的方法來學習新的知識。
五、本課程你的其他收穫及希望