- Keil Cx51 V7.0單片機高級語言編程與μVision2應(yīng)用實踐
- 徐愛鈞 彭秀華編著
- 683字
- 2018-12-29 19:18:33
第3章 函數(shù)
函數(shù)是C語言中的一種基本模塊,實際上,一個C語言程序就是由若干個模塊化的函數(shù)所構(gòu)成的。前面我們已經(jīng)看到,C語言程序總是由主函數(shù)main()開始,main()函數(shù)是一個控制程序流程的特殊函數(shù),它是程序的起點。在進行程序設(shè)計的過程中,如果所設(shè)計的程序較大,一般應(yīng)將其分成若干個子程序模塊,每個模塊完成一種特定的功能。在C語言中,子程序是用函數(shù)來實現(xiàn)的。對于一些需要經(jīng)常使用的子程序可以設(shè)計成一個專門的函數(shù)庫,以供反復(fù)調(diào)用。此外,Keil Cx51編譯器還提供了豐富的運行庫函數(shù),用戶可以根據(jù)需要隨時調(diào)用。這種模塊化的程序設(shè)計方法,可以大大提高編程效率和速度。
3.1 函數(shù)的定義
從用戶的角度來看,有兩種函數(shù):標準庫函數(shù)和用戶自定義函數(shù)。標準庫函數(shù)是Keil Cx51編譯器提供的,不需要用戶進行定義,可以直接調(diào)用。用戶自定義函數(shù)是用戶根據(jù)自己需要編寫的能實現(xiàn)特定功能的函數(shù),它必須先進行定義之后才能調(diào)用。函數(shù)定義的一般形式為:
函數(shù)類型 函數(shù)名(形式參數(shù)表) 形式參數(shù)說明 { 局部變量定義 函數(shù)體語句 }
其中,“函數(shù)類型”說明了自定義函數(shù)返回值的類型。
“函數(shù)名”是用標識符表示的自定義函數(shù)名字。
“形式參數(shù)表”中列出的是在主調(diào)用函數(shù)與被調(diào)用函數(shù)之間傳遞數(shù)據(jù)的形式參數(shù),形式參數(shù)的類型必須加以說明。ANSI C標準允許在形式參數(shù)表中對形式參數(shù)的類型進行說明。如果定義的是無參函數(shù),可以沒有形式參數(shù)表,但圓括號不能省略。
“局部變量定義”是對在函數(shù)內(nèi)部使用的局部變量進行定義。
“函數(shù)體語句”是為完成該函數(shù)的特定功能而設(shè)置的各種語句。
如果定義函數(shù)時只給出一對花括號{}而不給出其局部變量和函數(shù)體語句,則該函數(shù)為“空函數(shù)”,這種空函數(shù)也是合法的。在進行C語言模塊化程序設(shè)計時,各模塊的功能可通過函數(shù)來實現(xiàn)。開始時只設(shè)計最基本的模塊,其他作為擴充功能在以后需要時再加上。編寫程序時可在將來準備擴充的地方寫上一個空函數(shù),這樣可使程序的結(jié)構(gòu)清晰,可讀性好,而且易于擴充。
例3.1:定義一個計算整數(shù)的正整數(shù)次冪的函數(shù)。
int power(x, n) int x, n; { int i, p; p=1; for(i=1; i<=n; ++i) p=p*x; return(p); }
這里定義了一個返回值為整型值的函數(shù)power(),它有兩個形式參數(shù):x,n。形式參數(shù)的作用是接受從主調(diào)用函數(shù)傳遞過來的實際參數(shù)的值。上例中形式參數(shù)x和n被說明為int類型。花括號以內(nèi)的部分是自定義函數(shù)的函數(shù)體。上例中在函數(shù)體內(nèi)定義了兩個局部變量i和p,它們均為整型數(shù)據(jù)。
需要注意的是,形式參數(shù)的說明與函數(shù)體內(nèi)的局部變量定義是完全不同的兩個部分,前者應(yīng)寫在花括號的外面,而后者是函數(shù)體的一個組成部分,必須寫在花括號的里面。為了不發(fā)生混淆,ANSI C標準允許在形式參數(shù)表中對形式參數(shù)的類型進行說明,如上例可寫成:int power(int x, int n)。
在函數(shù)體中可以根據(jù)用戶自己的需要,設(shè)置各種不同的語句。這些語句應(yīng)能完成所需要的功能。上例在函數(shù)體中用一個for循環(huán)結(jié)構(gòu)完成一個整數(shù)的正整數(shù)次冪的計算,計算結(jié)果賦值給變量p。函數(shù)體中最后一條語句return(p)的作用是將p的值返回到主調(diào)用函數(shù)中去。return語句后面圓括號中的值稱為函數(shù)的返回值,圓括號可以省略,即return p和return(p)是等價的。
由于p是函數(shù)的返回值,因此在函數(shù)體中進行變量定義時,應(yīng)將變量p的類型定義得與函數(shù)本身的類型相一致。如果二者類型不一致,則函數(shù)調(diào)用時的返回值可能發(fā)生錯誤。如果函數(shù)體中沒有return語句,則該函數(shù)由函數(shù)體最后面的右閉花括號“}”返回。在這種情況下,函數(shù)的返回值是不確定的。
對于不需要有返回值的函數(shù),可以將該函數(shù)定義為void類型(空類型)。對于上例,如果定義為:void power(int x, int n),則可將函數(shù)體中的return語句去掉,這樣,編譯器會保證在函數(shù)調(diào)用結(jié)束時不使函數(shù)返回任何值。為了使程序減少出錯,保證函數(shù)的正確調(diào)用,凡是不要求有返回值的函數(shù),都應(yīng)將其定義成void類型。
例3.2:不同函數(shù)的定義方法。
char fun1(x, y) /* 定義一個char型函數(shù) */ int x; /* 說明形式參數(shù)的類型 */ char y; { char z; /* 定義函數(shù)內(nèi)部的局部變量 */ z=x+y; /* 函數(shù)體語句 */ return(z); /* 返回函數(shù)的值z,注意變量z與函數(shù)本身 } 的類型均為char型 */ int fun2(float a, float b) /* 定義一個int型函數(shù),在形式參數(shù)表中說 { 明形式參數(shù)的類型 */ int x; /* 定義函數(shù)內(nèi)部的局部變量 */ x=a-b; /* 函數(shù)體語句 */ return(x); /* 返回函數(shù)的值x,注意變量x與函數(shù)本身 } 的類型均為int型 */ long fun3() /* 定義一個long型函數(shù),它沒有形式參數(shù) */ { long x; /* 定義函數(shù)內(nèi)部的局部變量 */ int i, j; x=i*j; /* 函數(shù)體語句 */ return(x); /* 返回函數(shù)的值x,注意變量x與函數(shù)本身 } 的類型均為long型 */ void fun4(char a, char b) /* 定義一個無返回值的void型函數(shù) */ { char x; /* 局部變量定義 */ x=a+b; /* 函數(shù)體語句 */ } /* 函數(shù)不需要返回值,省略return語句 */ void fun5( ) /* 定義一個空函數(shù) */ { }
3.2 函數(shù)的調(diào)用
3.2.1 函數(shù)的調(diào)用形式
C語言程序中函數(shù)是可以互相調(diào)用的。所謂函數(shù)調(diào)用就是在一個函數(shù)體中引用另外一個已經(jīng)定義了的函數(shù),前者稱為主調(diào)用函數(shù),后者稱為被調(diào)用函數(shù)。函數(shù)調(diào)用的一般形式為:
函數(shù)名(實際參數(shù)表)
其中,“函數(shù)名”指出被調(diào)用的函數(shù)。
“實際參數(shù)表”中可以包含多個實際參數(shù),各個參數(shù)之間用逗號隔開。實際參數(shù)的值被傳遞給被調(diào)用函數(shù)中的形式參數(shù)。需要注意的是,函數(shù)調(diào)用中的實際參數(shù)與函數(shù)定義中的形式參數(shù)必須在個數(shù)、類型及順序上嚴格保持一致,以便將實際參數(shù)的值正確地傳遞給形式參數(shù)。否則在函數(shù)調(diào)用時會產(chǎn)生意想不到的結(jié)果。如果調(diào)用的是無參函數(shù),則可以沒有實際參數(shù)表,但圓括號不能省略。
在C語言中可以采用三種方式完成函數(shù)的調(diào)用。
(1)函數(shù)語句
在主調(diào)函數(shù)中將函數(shù)調(diào)用作為一條語句,例如:
fun1();
這是無參調(diào)用,它不要求被調(diào)用函數(shù)返回一個確定的值,只要求它完成一定的操作。
(2)函數(shù)表達式
在主調(diào)函數(shù)中將函數(shù)調(diào)用作為一個運算對象直接出現(xiàn)在表達式中,這種表達式稱為函數(shù)表達式。例如:
c = power(x,n) + power(y,m);
這其實是一個賦值語句,它包括兩個函數(shù)調(diào)用,每個函數(shù)調(diào)用都有一個返回值,將兩個返回值相加的結(jié)果,賦值給變量c。因此這種函數(shù)調(diào)用方式要求被調(diào)函數(shù)返回一個確定的值。
(3)函數(shù)參數(shù)
在主調(diào)函數(shù)中將函數(shù)調(diào)用作為另一個函數(shù)調(diào)用的實際參數(shù)。例如:
y=power(power(i, j), k);
其中,函數(shù)調(diào)用power(i, j)放在另一個函數(shù)調(diào)用power(power(i, j), k)的實際參數(shù)表中,以其返回值作為另一個函數(shù)調(diào)用的實際參數(shù)。這種在調(diào)用一個函數(shù)的過程中又調(diào)用了另外一個函數(shù)的方式,稱為嵌套函數(shù)調(diào)用。在輸出一個函數(shù)的值時經(jīng)常采用這種方法,例如:
printf("%d", power(i,j));
其中,函數(shù)調(diào)用power(i,j)是作為printf()函數(shù)的一個實際參數(shù)處理的,它也屬于嵌套函數(shù)調(diào)用方式。
3.2.2 對被調(diào)用函數(shù)的說明
與使用變量一樣,在調(diào)用一個函數(shù)之前(包括標準庫函數(shù)),必須對該函數(shù)的類型進行說明,即“先說明,后調(diào)用”。如果調(diào)用的是庫函數(shù),一般應(yīng)在程序的開始處用預(yù)處理命令#include將有關(guān)函數(shù)說明的頭文件包含進來。例如前面例子中經(jīng)常出現(xiàn)的預(yù)處理命令#include<stdio.h>,就是將與庫輸出函數(shù)printf()有關(guān)的頭文件stdio.h包含到程序文件中來。頭文件“stdio.h”中有關(guān)于庫輸入輸出函數(shù)的一些說明信息,如果不使用這個包含命令,庫輸入輸出函數(shù)就無法被正確地調(diào)用。
如果調(diào)用的是用戶自定義函數(shù),而且該函數(shù)與調(diào)用它的主調(diào)函數(shù)在同一個文件中,一般應(yīng)該在主調(diào)函數(shù)中對被調(diào)用函數(shù)的類型進行說明。函數(shù)說明的一般形式為:
類型標識符 被調(diào)用的函數(shù)名(形式參數(shù)表);
其中,“類型標識符”說明了函數(shù)返回值的類型。
“形式參數(shù)表”中說明各個形式參數(shù)的類型。
需要注意的是,函數(shù)的說明與函數(shù)的定義是完全不同的。函數(shù)的定義是對函數(shù)功能的確立,它是一個完整的函數(shù)單位。而函數(shù)的說明,只是說明了函數(shù)返回值的類型。二者在書寫形式上也不一樣,函數(shù)說明結(jié)束時在圓括號的后面需要有一個分號“;”作為結(jié)束標志,而在函數(shù)定義時,被定義函數(shù)名的圓括號后面沒有分號“;”,即函數(shù)定義還未結(jié)束,后面應(yīng)接著書寫形式參數(shù)說明和被定義的函數(shù)體部分。
如果被調(diào)函數(shù)是在主調(diào)函數(shù)前面定義的,或者已經(jīng)在程序文件的開始處說明了所有被調(diào)函數(shù)的類型,在這兩種情況下可以不必再在主調(diào)函數(shù)中對被調(diào)函數(shù)進行說明。也可以將所有用戶自定義函數(shù)的說明另存為一個專門的頭文件,需要時用#include將其包含到主程序中去。
C語言程序中不允許在一個函數(shù)定義的內(nèi)部包括另一個函數(shù)的定義,即不允許嵌套函數(shù)定義。但是允許在調(diào)用一個函數(shù)的過程中包含另一個函數(shù)調(diào)用,即嵌套函數(shù)調(diào)用在C語言程序中是允許的。
例3.3:函數(shù)調(diào)用的例子。
#include <stdio.h> int Max(int x, int y); /* 對被調(diào)用函數(shù)進行說明 */ void main() { /* 主函數(shù) */ int a, b; /* 主函數(shù)的局部變量定義 */ printf("input a and b: \n"); scanf("%d %d", &a, &b); /* 調(diào)用庫輸入函數(shù),從鍵盤獲得a、b的值 */ printf("Max=%d", Max(a, b)); /* 調(diào)用庫輸出函數(shù),輸出a、b中較大者的值*/ while(1); } int Max(int x, int y) { /* 功能函數(shù)定義 */ int z; /* 局部變量定義 */ if(x>y) /* 函數(shù)體語句 */ z=x; else z=y; return(z); }
程序執(zhí)行結(jié)果:
input a and b: 123 456 回車 Max=456
在這個例子中,主函數(shù)main()先調(diào)用庫輸入函數(shù)sacnf(),從鍵盤輸入兩個值分別賦值給局部變量a和b,然后調(diào)用庫輸出函數(shù)printf()將a、b中較大者輸出。在調(diào)用庫輸出函數(shù)printf()的過程中又調(diào)用了自定義功能函數(shù)Max(),將鍵盤輸入的a、b的值作為實際參數(shù)傳遞給Max()函數(shù)中的形式參數(shù)x、y。在Max()函數(shù)中對實際輸入值進行比較以獲得較大者的值。這也是一個嵌套函數(shù)調(diào)用的例子,圖3.1是例3.3程序中函數(shù)調(diào)用的執(zhí)行過程。

圖3.1 函數(shù)的嵌套調(diào)用過程
3.2.3 函數(shù)的參數(shù)與返回值
通常在進行函數(shù)調(diào)用時,主調(diào)用函數(shù)與被調(diào)用函數(shù)之間具有數(shù)據(jù)傳遞關(guān)系。這種數(shù)據(jù)傳遞是通過函數(shù)的參數(shù)實現(xiàn)的。在定義一個函數(shù)時,位于函數(shù)名后面圓括號中的變量名稱為“形式參數(shù)”,而在調(diào)用函數(shù)時,函數(shù)名后面括號中的表達式稱為“實際參數(shù)”。形式參數(shù)在未發(fā)生函數(shù)調(diào)用之前,不占用內(nèi)存單元,因而也是沒有值的。只有在發(fā)生函數(shù)調(diào)用時才為它分配內(nèi)存單元,同時獲得從主調(diào)用函數(shù)中實際參數(shù)傳遞過來的值。函數(shù)調(diào)用結(jié)束后,它所占用的內(nèi)存單元也被釋放。
實際參數(shù)可以是常數(shù),也可以是變量或表達式,但要求它們具有確定的值。進行函數(shù)調(diào)用時,主調(diào)用函數(shù)將實際參數(shù)的值傳遞給被調(diào)用函數(shù)中的形式參數(shù)。為了完成正確的參數(shù)傳遞,實際參數(shù)的類型必須與形式參數(shù)的類型一致,如果兩者不一致,則會發(fā)生“類型不匹配”錯誤。
例3.4:計算一個整數(shù)的正整數(shù)次冪。
#include<stdio.h> main() { int power(int x, int n); int a, b, c; printf("please input X and n: \n"); scanf("%d %d",&a, &b); c=power(a, b); printf("\n%d to the power of %d is: %d", a, b, c); while(1); } int power(int x, int n) { int i, p; p=1; for(i=1; i<=n; ++i) p=p*x; return(p); }
程序執(zhí)行結(jié)果:
please input X and n: 5, 3 回車 5 to the power of 3 is 125
在這個程序中定義了一個計算整數(shù)的正整數(shù)次冪的函數(shù)int power(int x, int n);它有兩個整型的形式參數(shù)x和n。在程序開始時,變量x和n是不占用內(nèi)存單元的,因此也是沒有值的。在主函數(shù)main()中先從鍵盤輸入兩個整數(shù)值a和b,然后通過函數(shù)調(diào)用語句c=power(a, b);將實際參數(shù)a和b的值傳遞給被調(diào)用函數(shù)power()中的形式參數(shù)。調(diào)用發(fā)生時,形式參數(shù)變量x和n被賦以實際參數(shù)a和b的值,從而使函數(shù)power()能按實際參數(shù)的值進行計算。從這個例子可以看到,形式參數(shù)和實際參數(shù)可以不同名,但它們的類型必須要一致。
一般情況下,希望通過函數(shù)調(diào)用使主調(diào)用函數(shù)獲得一個確定的值,這就是函數(shù)的返回值。例如,上例中的函數(shù)調(diào)用語句c=power(a, b);就是將函數(shù)power()的返回值賦給變量c。函數(shù)的返回值是通過return語句獲得的,如果希望從被調(diào)用函數(shù)中帶回一個值到主調(diào)用函數(shù),被調(diào)用函數(shù)中必須包含有return語句。
一個函數(shù)中可以有一個以上的return語句,執(zhí)行到哪一個return語句,哪一個return語句起作用。return后面可以跟一個表達式,例如,return(x>y? x: y);這種寫法只用一條return語句即可同時完成表達式的計算和函數(shù)值的返回。return后面還可以跟另外一個已定義了的函數(shù)名,例如:return keyval(rdkey);采用這種寫法可實現(xiàn)函數(shù)的嵌套調(diào)用,即在函數(shù)返回的同時調(diào)用另一個函數(shù)。
函數(shù)返回值的類型確定了該函數(shù)的類型,因此在定義一個函數(shù)時,函數(shù)本身的類型應(yīng)與return語句中變量或表達式的類型一致。例如,上例中power()函數(shù)被定義為int類型,return語句中的變量p也被定義為int類型。如果函數(shù)類型與return語句中表達式的值類型不一致,則以函數(shù)的類型為準。對于返回的數(shù)值數(shù)據(jù)可以自動進行類型轉(zhuǎn)換,即函數(shù)的類型決定返回值的類型。如果不需要被調(diào)用函數(shù)返回一個確定的值,則可以不要return語句,同時應(yīng)將被調(diào)用函數(shù)定義成void類型。事實上,main()函數(shù)就是一個典型的沒有返回值的函數(shù),因此可以將其寫成void main()的形式。由于void類型的函數(shù)沒有return語句,因此在一個void類型函數(shù)的調(diào)用結(jié)束時,將從該函數(shù)的最后一個花括號處返回到主調(diào)用函數(shù)。
例3.5:使用void類型函數(shù)的例子。
#include<stdio.h> void main() { /* void類型的主函數(shù) */ void prn_char(char x); /* 功能函數(shù)說明 */ prn_char('w'); /* 功能函數(shù)調(diào)用 */ while(1); } void prn_char(char x) /* 將功能函數(shù)定義為void類型,無返回值 */ { printf("%c has ASCII value %bd\n",x,x); } /* void型功能函數(shù)從此處返回主調(diào)用函數(shù) */
程序執(zhí)行結(jié)果:
w has ASCII value 119
3.2.4 實際參數(shù)的傳遞方式
在進行函數(shù)調(diào)用時,必須用主調(diào)函數(shù)中的實際參數(shù)來替換被調(diào)函數(shù)中的形式參數(shù),這就是所謂的參數(shù)傳遞。在C語言中,對于不同類型的實際參數(shù),有三種不同的參數(shù)傳遞方式。
(1)基本類型的實際參數(shù)傳遞
當函數(shù)的參數(shù)是基本類型的變量時,主調(diào)函數(shù)將實際參數(shù)的值傳遞給被調(diào)函數(shù)中的形式參數(shù),這種方式稱為值傳遞。前面講過,函數(shù)中的形式參數(shù)在未發(fā)生數(shù)調(diào)用之前是不占用內(nèi)存單元的,只有在進行函數(shù)調(diào)用時才為其分配臨時存儲單元。而函數(shù)的實際參數(shù)是要占用確定的存儲單元的。值傳遞方式是將實際參數(shù)的值傳遞到為被調(diào)函數(shù)中形式參數(shù)分配的臨時存儲單元中,函數(shù)調(diào)用結(jié)束后,臨時存儲單元被釋放,形式參數(shù)的值也就不復(fù)存在,但實際參數(shù)所占用的存儲單元保持原來的值不變。這種參數(shù)傳遞方式在執(zhí)行被調(diào)函數(shù)時,如果形式參數(shù)的值發(fā)生變化,可以不必擔心主調(diào)函數(shù)中實際參數(shù)的值會受到影響。因此值傳遞是一種單向傳遞。
(2)數(shù)組類型的實際參數(shù)傳遞
當函數(shù)的參數(shù)是數(shù)組類型的變量時,主調(diào)函數(shù)將實際參數(shù)數(shù)組的起始地址傳遞到被調(diào)函數(shù)中形式參數(shù)的臨時存儲單元,這種方式稱為地址傳遞。地址傳遞方式在執(zhí)行被調(diào)函數(shù)時,形式參數(shù)通過實際參數(shù)傳來的地址,直接到主調(diào)函數(shù)中去存取相應(yīng)的數(shù)組元素,故形式參數(shù)的變化會改變實際參數(shù)的值。因此地址傳遞是一種雙向傳遞。
(3)指針類型的實際參數(shù)傳遞
當函數(shù)的參數(shù)是指針類型的變量時,主調(diào)函數(shù)將實際參數(shù)的地址傳遞給被調(diào)函數(shù)中形式參數(shù)的臨時存儲單元,因此也屬于地址傳遞。在執(zhí)行被調(diào)函數(shù)時,也是直接到主調(diào)函數(shù)中去訪問實際參數(shù)變量,在這種情況下,形式參數(shù)的變化會改變實際參數(shù)的值。
前面介紹的一些函數(shù)調(diào)用中所涉及的都是基本類型的實際參數(shù)傳遞,這種參數(shù)傳遞方式比較容易理解和應(yīng)用。關(guān)于數(shù)組類型和指針類型實際參數(shù)的傳遞較為復(fù)雜,將在第4章中詳細介紹。
3.3 函數(shù)的遞歸調(diào)用與再入函數(shù)
如果在調(diào)用一個函數(shù)的過程中又間接或直接地調(diào)用該函數(shù)本身,稱為函數(shù)的遞歸調(diào)用。例如,計算階乘函數(shù)f (n)=n!,可以先計算f (n-1)=(n-1)!,而計算f (n-1)時又可以先計算f (n-2)=(n-2)!,這就是遞歸算法。再入函數(shù)是一種可以在函數(shù)體內(nèi)直接或間接調(diào)用其自身的一種函數(shù),顯然再入函數(shù)是可以進行遞歸調(diào)用的。
Keil Cx51編譯器采用一個擴展關(guān)鍵字reentrant,作為定義函數(shù)時的選項,需要將一個函數(shù)定義為再入函數(shù)時,只要在函數(shù)名后面加上關(guān)鍵字reentrant即可:
函數(shù)類型 函數(shù)名(形式參數(shù)表)[reentrant]
再入函數(shù)可被遞歸調(diào)用,無論何時,包括中斷服務(wù)函數(shù)在內(nèi)的任何函數(shù)都可調(diào)用再入函數(shù)。與非再入函數(shù)的參數(shù)傳遞和局部變量的存儲分配方法不同,Cx51編譯器為再入函數(shù)生成一個模擬棧,通過這個模擬棧來完成參數(shù)傳遞和存放局部變量。模擬棧所在的存儲器空間根據(jù)再入函數(shù)存儲器模式的不同,可以是DATA、PDATA或XDATA存儲器空間。當程序中包含有多種存儲器模式的再入函數(shù)時,Cx51編譯器為每種模式單獨建立一個模擬棧并獨立管理各自的棧指針。對于再入函數(shù)有如下規(guī)定。
① 再入函數(shù)不能傳送bit類型的參數(shù),也不能定義一個局部位變量,再入函數(shù)不能包括位操作以及8051系列單片機的可位尋址區(qū)。
② 與PL/M51兼容的函數(shù)不能具有reentrant屬性,也不能調(diào)用再入函數(shù)。
③ 在編譯時存儲器模式的基礎(chǔ)上為再入函數(shù)在內(nèi)部或外部存儲器中建立一個模擬堆棧區(qū),稱為再入棧。在Small模式下再入棧位于IDATA區(qū),在compact模式下再入棧位于PDATA區(qū),在Large模式下再入棧位于XDATA區(qū)。再入函數(shù)的局部變量及參數(shù)都被放在再入棧中,從而使再入函數(shù)可以進行遞歸調(diào)用。而非再入函數(shù)的局部變量被放在再入棧之外的暫存區(qū)內(nèi),如果對非再入函數(shù)進行遞歸調(diào)用,則上次調(diào)用時使用的局部變量數(shù)據(jù)將被覆蓋。
④ 在同一個程序中可以定義和使用不同存儲器模式的再入函數(shù),任意模式的再入函數(shù)不能調(diào)用不同模式的再入函數(shù),但可任意調(diào)用非再入函數(shù)。
⑤ 在參數(shù)的傳遞上,實際參數(shù)可以傳遞給間接調(diào)用的再入函數(shù)。無再入屬性的間接調(diào)用函數(shù)不能包含調(diào)用參數(shù),但是可以使用定義的全局變量來進行參數(shù)傳遞。
例3.6:利用函數(shù)的遞歸調(diào)用計算整數(shù)的階乘。
#include<stdio.h> fac(int n) reentrant { if (n<1) return(1); else return(n*fac(n-1)); } main() { int n; printf("please input a number: \n"); scanf("%d", &n); printf("fac(%d)=%d\n", n, fac(n)); while(1); }
程序執(zhí)行結(jié)果:
please input a number 3 回車 fac(3)=6
在這個程序中定義了一個再入函數(shù)fac(n),它是用來計算階乘n!的函數(shù)。在fac()的函數(shù)體中又調(diào)用了fac()函數(shù)本身,因此這是一種函數(shù)的遞歸調(diào)用。再入函數(shù)在進行遞歸調(diào)用時,新的局部變量和參數(shù)在再入棧中重新分配存儲單元,并以新的變量重新開始執(zhí)行。每次遞歸調(diào)用返回時,前面壓入的局部變量和參數(shù)會從再入棧中彈出,并恢復(fù)到上次調(diào)用自身的地方繼續(xù)執(zhí)行。如果是非再入函數(shù)進行遞歸調(diào)用,每次調(diào)用函數(shù)自身時,上次調(diào)用時使用的局部變量數(shù)據(jù)將被覆蓋,因而在遞歸調(diào)用結(jié)束時不能得到正確的結(jié)果。對于例3.6的程序,如果將函數(shù)fac(n)定義成非再入函數(shù),則程序的運行結(jié)果為0,顯然這是不正確的。
采用函數(shù)的遞歸調(diào)用可使程序的結(jié)構(gòu)緊湊,但是遞歸調(diào)用要求采用再入函數(shù),以便利用再入棧來保存有關(guān)的局部變量數(shù)據(jù),從而要占據(jù)較大的內(nèi)存空間。另外遞歸調(diào)用時對函數(shù)的處理速度也比較慢,因此一般情況下應(yīng)盡量避免采用函數(shù)遞歸調(diào)用,定義函數(shù)時應(yīng)盡量避免使用再入屬性。
3.4 中斷服務(wù)函數(shù)與寄存器組定義
Keil Cx51編譯器支持在C語言源程序中直接編寫8051單片機的中斷服務(wù)函數(shù)程序,從而減輕了采用匯編語言編寫中斷服務(wù)程序的煩瑣程度。為了在C語言源程序中直接編寫中斷服務(wù)函數(shù)的需要,Keil Cx51編譯器對函數(shù)的定義進行了擴展,增加了一個擴展關(guān)鍵字interrupt,它是函數(shù)定義時的一個選項,加上這個選項即可以將一個函數(shù)定義成中斷服務(wù)函數(shù)。定義中斷服務(wù)函數(shù)的一般形式為:
函數(shù)類型 函數(shù)名(形式參數(shù)表)[interrupt n][using n]
關(guān)鍵字interrupt后面的n是中斷號,n的取值范圍為0~31。編譯器從8n+3處產(chǎn)生中斷向量,具體的中斷號n和中斷向量取決于8051系列單片機芯片型號,常用中斷源和中斷向量如表3-1所示。
表3-1 常用中斷號與中斷向量

8051系列單片機可以在片內(nèi)RAM中使用4個不同的工作寄存器組,每個寄存器組中包含8個工作寄存器(R0~R7)。Keil Cx51編譯器擴展了一個關(guān)鍵字using,專門用來選擇8051單片機中不同的工作寄存器組。using后面的n是一個0~3的常整數(shù),分別選中4個不同的工作寄存器組。在定義一個函數(shù)時using是一個選項,如果不用該選項,則由編譯器自動選擇一個寄存器組作絕對寄存器組訪問。需要注意的是,關(guān)鍵字using和interrupt的后面都不允許跟帶運算符的表達式。
關(guān)鍵字using對函數(shù)目標代碼的影響如下:在函數(shù)的入口處將當前工作寄存器組保護到堆棧中;指定的工作寄存器內(nèi)容不會改變;函數(shù)退出之前將被保護的工作寄存器組從堆棧中恢復(fù)。
使用關(guān)鍵字using在函數(shù)中確定一個工作寄存器組時必須十分小心,要保證任何寄存器組的切換都只在仔細控制的區(qū)域內(nèi)發(fā)生,如果不做到這一點將產(chǎn)生不正確的函數(shù)結(jié)果。另外還要注意,帶using屬性的函數(shù)原則上不能返回bit類型的值。并且關(guān)鍵字using不允許用于外部函數(shù)。
關(guān)鍵字interrupt也不允許用于外部函數(shù),它對中斷函數(shù)目標代碼的影響如下:在進入中斷函數(shù)時,特殊功能寄存器ACC、B、DPH、DPL、PSW將被保存入棧;如果不使用關(guān)鍵字using進行工作寄存器組切換,則將中斷函數(shù)中所用到的全部工作寄存器都入棧保存;函數(shù)退出之前所有的寄存器內(nèi)容出棧恢復(fù);中斷函數(shù)由8051單片機指令RETI結(jié)束。
下面給出一個帶有寄存器組切換的中斷函數(shù)定義的例子,該例中還給出了C51編譯器所生成的8051單片機的指令代碼。
例3.7:帶有寄存器組切換的中斷函數(shù)定義。
stmt level source 1 #pragma cd 2 #include <reg51.h> 3 extern void alfunc(bit b0); 4 extern bit alarm; 5 int DTIMES; 6 char bdata flag; 7 sbit flag0=flag^0; 8 int dtime1=0x0a; 9 10 void int0 () interrupt 0 using 1 { 11 1 TR1=0; 12 1 flag0=!flag0; 13 1 DTIMES=dtime1; 14 1 dtime1=0; 15 1 TR1=1; 16 1 } 17 18 void timer1 () interrupt 3 using 3 { 19 1 alfunc(alarm=1); 20 1 TH1=0x3c; 21 1 TL1=0xB0; 22 1 dtime1=dtime1+1; 23 1 if (dtime1==0) 24 1 { 25 2 P0=0; 26 2 } 27 1 } 28 ASSEMBLY LISTING OF GENERATED OBJECT CODE ;FUNCTION int0 (BEGIN) ;SOURCE LINE # 10 ;SOURCE LINE # 11 0000 C28E CLR TR1 ;SOURCE LINE # 12 0002 B200 R CPL flag0 ;SOURCE LINE # 13 0004850000 R MOV DTIMES,dtime1 0007850000 R MOV DTIMES+01H,dtime1+01H ;SOURCE LINE # 14 000A 750000 R MOV dtime1,#00H 000D 750000 R MOV dtime1+01H,#00H ;SOURCE LINE # 15 0010 D28E SETB TR1 ;SOURCE LINE # 16 0012 32 RETI ;FUNCTION int0 (END) ;FUNCTION timer1 (BEGIN) 0000 C0E0 PUSH ACC 0002 C0F0 PUSH B 0004 C083 PUSH DPH 0006 C082 PUSH DPL 0008 C0D0 PUSH PSW 000A 75D018 MOV PSW,#018H ;SOURCE LINE # 18 ;SOURCE LINE # 19 000D D3 SETB C 000E 9200 E MOV alarm,C 00109200 E MOV ?alfunc?BIT,C 0012120000 E LCALL alfunc ;SOURCE LINE # 20 0015758D3C MOV TH1,#03CH ;SOURCE LINE # 21 0018758BB0 MOV TL1,#0B0H ;SOURCE LINE # 22 001B 0500 R INC dtime1+01H 001D E500 R MOV A,dtime1+01H 001F 7002 JNZ ?C0004 00210500 R INC dtime1 0023 ?C0004: ;SOURCE LINE # 23 00234500 R ORL A,dtime1 00257002 JNZ ?C0003 ;SOURCE LINE # 24 ;SOURCE LINE # 25 0027 F580 MOV P0,A ;SOURCE LINE # 26 ;SOURCE LINE # 27 0029 ?C0003: 0029 D0D0 POP PSW 002B D082 POP DPL 002D D083 POP DPH 002F D0F0 POP B 0031 D0E0 POP ACC 0033 32 RETI ;FUNCTION timer1 (END)
編寫8051單片機中斷函數(shù)時應(yīng)遵循以下規(guī)則。
① 中斷函數(shù)不能進行參數(shù)傳遞,如果中斷函數(shù)中包含任何參數(shù)聲明都將導(dǎo)致編譯出錯。
② 中斷函數(shù)沒有返回值,如果企圖定義一個返回值將得到不正確的結(jié)果。因此建議在定義中斷函數(shù)時將其定義為void類型,以明確說明沒有返回值。
③ 在任何情況下都不能直接調(diào)用中斷函數(shù),否則會產(chǎn)生編譯錯誤。因為中斷函數(shù)的退出是由8051單片機指令RETI完成的,RETI指令影響8051單片機的硬件中斷系統(tǒng)。如果在沒有實際中斷請求的情況下直接調(diào)用中斷函數(shù),RETI指令的操作結(jié)果會產(chǎn)生一個致命的錯誤。
④ 如果在中斷函數(shù)中調(diào)用了其他函數(shù),則被調(diào)用函數(shù)所使用的寄存器組必須與中斷函數(shù)相同。用戶必須保證按要求使用相同的寄存器組,否則會產(chǎn)生不正確的結(jié)果,這一點必須引起足夠的注意。如果定義中斷函數(shù)時沒有使用using選項,則由編譯器自動選擇一個寄存器組作絕對寄存器組訪問。另外,由于中斷的產(chǎn)生不可預(yù)測,中斷函數(shù)對其他函數(shù)的調(diào)用可能形成遞規(guī)調(diào)用,需要時可將被中斷函數(shù)所調(diào)用的其他函數(shù)定義成再入函數(shù)。
⑤ Keil Cx51編譯器從絕對地址8n+3處產(chǎn)生一個中斷向量,其中n為中斷號。該向量包含一個到中斷函數(shù)入口地址的絕對跳傳。在對源程序編譯時,可用編譯控制命令NOINTVECTOR抑制中斷向量的產(chǎn)生,從而使用戶有能力從獨立的匯編程序模塊中提供中斷向量。
3.5 函數(shù)變量的存儲方式
3.5.1 局部變量與全局變量
按照變量的有效作用范圍可劃分為局部變量和全局變量。局部變量是在一個函數(shù)內(nèi)部定義的變量,該變量只在定義它的那個函數(shù)范圍以內(nèi)有效。在此函數(shù)之外局部變量即失去意義,因而也就不能使用這些變量了。不同的函數(shù)可以使用相同的局部變量名,由于它們的作用范圍不同,不會相互干擾。函數(shù)的形式參數(shù)也屬于局部變量。在一個函數(shù)內(nèi)部的復(fù)合語句中也可以定義局部變量,該局部變量只在該復(fù)合語句中有效。
全局變量是在函數(shù)外部定義的變量,又稱為外部變量。全局變量可以為多個函數(shù)共同使用,其有效作用范圍是從它定義的位置開始到整個程序文件結(jié)束。如果全局變量定義在一個程序文件的開始處,則在整個程序文件范圍內(nèi)都可以使用它。如果一個全局變量不是在程序文件的開始處定義的,但又希望在它的定義點之前的函數(shù)中引用該變量,這時應(yīng)在引用該變量的函數(shù)中用關(guān)鍵字extern將其說明為“外部變量”。另外,如果在一個程序模塊文件中引用另一個程序模塊文件中定義的變量時,也必須用extern進行說明。
外部變量說明與外部變量定義是不相同的。外部變量定義只能有一次,定義的位置在所有函數(shù)之外。而同一個程序文件中的外部變量說明可以有多次,說明的位置在需要引用該變量的函數(shù)之內(nèi)。外部變量說明的作用只是聲明該變量是一個已經(jīng)在外部定義過了的變量而已。
如果在同一個程序文件中,全局變量與局部變量同名,則在局部變量的有效作用范圍之內(nèi),全局變量不起作用。換句話說,局部變量的優(yōu)先級比全局變量高。在編寫C語言程序時,不是特別必要的地方一般不要使用全局變量,而應(yīng)當盡可能地使用局部變量。這是因為局部變量只在使用它時,才為其分配內(nèi)存單元,而全局變量在整個程序的執(zhí)行過程中都要占用內(nèi)存單元。另外,如果使用全局變量過多,在各個函數(shù)執(zhí)行時都有可能改變?nèi)肿兞康闹担谷藗冸y以清楚地判斷出在各個程序執(zhí)行點處全局變量的值,這樣會使降低程序的通用性和可讀性。
還有一點需要說明,如果程序中的全局變量在定義時賦給了初值,按ANSI C標準規(guī)定,在程序進入main()函數(shù)之前必須先對該全局變量進行初始化。這是由連接定位器BL51對目標程序連接定位時,在最后生成的目標代碼中自動加入一段運行庫“INIT.OBJ”來實現(xiàn)的。由于增加了這么一段代碼,程序的長度會增加,運行速度也會受到影響。因此要限制使用全局變量。
下面通過一個例子來說明局部變量與全局變量的區(qū)別。
例3.8:局部變量與全局變量的區(qū)別。
#include<stdio.h> int a=3, b=5; /* 定義a、b為全局變量,并賦以初值 */ max(int a, int b) { /* 形參a、b為局部變量 */ int c; /* 定義c為局部變量 */ c=a>b? a:b; return(c); } main() { int a=8; /* 定義a為局部變量 */ printf("%d", max(a,b)); while(1); }
程序執(zhí)行結(jié)果:
8
這個程序中故意使用了相同的變量名a和b,請讀者仔細區(qū)別它們的作用范圍。程序的第一行將a和b定義成全局變量并且賦了初值,由于具有初值的全局變量需要先行初始化,因此讀者如果用dScope51對這個例子程序進行調(diào)試,可以看到程序在進入main()函數(shù)之前,除了要執(zhí)行一段啟動程序STARTUP的代碼之外,還需要執(zhí)行一段全局變量初始化程序INIT的代碼。
第二行開始是定義一個求最大值函數(shù)max(),其作用是求得a和b中較大者的值。這里的a和b是max()函數(shù)的形式參數(shù),屬于局部變量。外部變量a和b在函數(shù)max()內(nèi)部不起作用,即形式參數(shù)a和b的值不再是3和5,它們的值是通過主調(diào)函數(shù)中的實際參數(shù)傳遞過來的。
程序的最后四行是main()函數(shù),在main()函數(shù)內(nèi)部定義了一個局部變量a并賦值為8,全局變量a在這里不起作用,而全局變量b在此范圍內(nèi)有效。因此printf()函數(shù)中的max(a, b)相當于max(8, 5),故程序的最后執(zhí)行結(jié)果為8。
3.5.2 變量的存儲種類
按變量的有效作用范圍可以將其劃分為局部變量和全局變量;還可以按變量的存儲方式為其劃分存儲種類。在C語言中變量有四種存儲種類,即自動變量(auto)、外部變量(extern)、靜態(tài)變量(static)和寄存器變量(register)。這四種存儲種類與全局變量和局部變量之間的關(guān)系如圖3.2所示。

圖3.2 變量的存儲種類
1.自動變量(auto)
定義一個變量時,在變量名前面加上存儲種類說明符“auto”,即將該變量定義為自動變量。自動變量是C語言中使用最為廣泛的一類變量。按照默認規(guī)則,在函數(shù)體內(nèi)部或復(fù)合語句內(nèi)部定義的變量,如果省略存儲種類說明,該變量即為自動變量。習(xí)慣上通常采用默認形式,例如:
{ char x; int y; ... }
等價于
{ auto char x; auto int y; ... }
自動變量的作用范圍在定義它的函數(shù)體或復(fù)合語句內(nèi)部,只有在定義它的函數(shù)被調(diào)用,或是定義它的復(fù)合語句被執(zhí)行時,編譯器才為其分配內(nèi)存空間,開始其生存期。當函數(shù)調(diào)用結(jié)束返回,或復(fù)合語句執(zhí)行結(jié)束時,自動變量所占用的內(nèi)存空間就被釋放,變量的值當然也就不復(fù)存在,其生存期結(jié)束。當函數(shù)被再次調(diào)用或復(fù)合語句被再次執(zhí)行,編譯器又會為它們內(nèi)部的自動變量重新分配內(nèi)存空間,但它不會保留上次運行時的值,而必須被重新賦值。因此自動變量始終是相對于函數(shù)或復(fù)合語句的局部變量。
2.外部變量(extern)
使用存儲種類說明符“extern”定義的變量稱為外部變量。按照默認規(guī)則,凡是在所有函數(shù)之前,在函數(shù)外部定義的變量都是外部變量,定義時可以不寫extern說明符。但是,在一個函數(shù)體內(nèi)說明一個已在該函數(shù)體外或別的程序模塊文件中定義過的外部變量時,則必須使用extern說明符。一個外部變量被定義之后,它就被分配了固定的內(nèi)存空間。外部變量的生存期為程序的整個執(zhí)行時間,即在程序的執(zhí)行期間外部變量可被隨意使用,當一條復(fù)合語句執(zhí)行完畢或是從某一個函數(shù)返回時,外部變量的存儲空間并不被釋放,其值也仍然保留。因此外部變量屬于全局變量。
C語言允許將大型程序分解為若干個獨立的程序模塊文件,各個模塊可分別進行編譯,然后再將它們連接在一起。在這種情況下,如果某個變量需要在所有程序模塊文件中使用,只要在一個程序模塊文件中將該變量定義成全局變量,而在其他程序模塊文件中用extern說明該變量是已被定義過的外部變量就可以了。
函數(shù)是可以相互調(diào)用的,因此函數(shù)都具有外部存儲種類的屬性。定義函數(shù)時如果冠以關(guān)鍵字extern即將其明確定義為一個外部函數(shù)。例如,extern int func2(char a, b)。如果在定義函數(shù)時省略關(guān)鍵字extern,則隱含為外部函數(shù)。如果要調(diào)用一個在本程序模塊文件以外的其他模塊文件所定義的函數(shù),則必須用關(guān)鍵字extern說明被調(diào)用函數(shù)是一個外部函數(shù)。對于具有外部函數(shù)相互調(diào)用的多模塊程序,利用μVision51集成開發(fā)環(huán)境很容易完成編譯連接。
這個例子中有兩個程序模塊文件“ex1.c”和“ex2.c”,可以在μVision51環(huán)境下將它們分別添加到一個項目文件“ex.prj”中,然后執(zhí)行Project菜單中的Make:Updat Project選項即可將它們連接在一起,生成OMF51絕對目標文件ex,絕對目標文件可以裝入dScope51中進行仿真調(diào)試。
例3.9:多模塊程序。
(程序模塊1 文件名為ex1.c) #include<stdio.h> int x = 5; void main() { extern void fun1(); /* 說明函數(shù)fun1在其他文件中定義 */ extern int fun2(int y); /* 說明函數(shù)fun2在其他文件中定義 */ fun1(); fun1(); fun1(); printf("\n%d %d\n",x,fun2(x)); while(1); } (程序模塊2 文件名為ex2.c) #include <stdio.h> extern int x; /* 說明變量x在其他文件中定義 */ void fun1() { static int a = 5; int b = 5; printf("%d %d %d | ",a,b,x); a -= 2; b -= 2; x -= 2; printf("%d %d %d\n",a,b,x); } int fun2(int y) { return( 35 * x * y ); }
程序的執(zhí)行結(jié)果為:
5 5 5 | 3 3 3 3 5 3 | 1 3 1 1 5 1 | -1 3 -1 -1 35
由于C語言不允許在一個函數(shù)體內(nèi)嵌套定義另一個函數(shù),為了能夠訪問不同文件中各個函數(shù)的變量,除了可以采用我們在前面介紹過的參數(shù)傳遞方法之外,還可以采用外部變量的方法。上面的例子就說明了這一點。需要指出的是,盡管使用外部變量在不同函數(shù)之間傳遞數(shù)據(jù)有時比使用函數(shù)的參數(shù)更為方便,但是當外部變量較多時,會增加程序調(diào)試排錯時的困難,使程序不便于維護。另外,不通過參數(shù)傳遞而直接在函數(shù)中改變?nèi)肿兞康闹担袝r還會發(fā)生一些意想不到的副作用。因此一般情況下最好還是使用函數(shù)的參數(shù)來傳遞數(shù)據(jù)。
3.靜態(tài)變量(static)
使用存儲種類說明符“static”定義的變量稱為靜態(tài)變量。在例3.9的模塊2程序文件中使用了一個靜態(tài)變量:static int a=5。由于這個變量是在函數(shù)fun1()內(nèi)部定義的,因此稱為內(nèi)部靜態(tài)變量或局部靜態(tài)變量。局部靜態(tài)變量不像自動變量那樣只有當函數(shù)調(diào)用它時才存在,退出函數(shù)后它就消失,局部靜態(tài)變量始終都是存在的,但只能在定義它的函數(shù)內(nèi)部進行訪問,退出函數(shù)之后,變量的值仍然保持,但不能進行訪問。
還有一種全局靜態(tài)變量,它是在函數(shù)外部被定義的,作用范圍從它的定義點開始,一直到程序結(jié)束。當一個C語言程序由若干個模塊文件所組成時,全局靜態(tài)變量始終存在,但它只能在被定義的模塊文件中訪問,其數(shù)據(jù)值可為該文件內(nèi)的所有函數(shù)共享,退出該文件后,雖然變量的值仍然保持著,但不能被其他模塊文件訪問。
局部靜態(tài)變量是一種在兩次函數(shù)調(diào)用之間仍能保持其值的局部變量。有些程序需要在多次調(diào)用之間仍然保持變量的值,使用自動變量無法實現(xiàn)這一點,使用全局變量有時又會帶來意外的副作用,這時就可采用局部靜態(tài)變量。
例3.10:局部靜態(tài)變量的使用——計算并輸出1~5的階乘值。
#include<stdio.h> int fac(int n) { static int f=1; f=f*n; return(f); } main() { int i; for (i=1; i<=5; i++) printf(“%d! = %d\n”, i, fac(i)); while(1); }
程序執(zhí)行結(jié)果:
1! = 1 2! = 2 3! = 6 4! = 24 5! = 120
在這個程序中,一共調(diào)用了5次計算階乘的函數(shù)fac(i),每次調(diào)用后輸出一個階乘值i!,同時保留這個i!值,以便下次再乘(i+1)。由此可見,如果要保留函數(shù)上一次調(diào)用結(jié)束時的值,或是在初始化之后變量只被引用而不改變其值,則這時使用局部靜態(tài)變量較為方便,以免在每次調(diào)用時都要重新進行賦值。但是,使用局部靜態(tài)變量需要占用較多的內(nèi)存空間,而且降低了程序的可讀性,當調(diào)用次數(shù)較多時往往弄不清局部靜態(tài)變量的當前值是什么。因此,建議不要多用局部靜態(tài)變量。
全局靜態(tài)變量是一種作用范圍受限制的外部變量,它的有效作用范圍從其定義點開始直至程序文件的末尾,而且只有在定義它的程序模塊文件中才能對它進行訪問。全局靜態(tài)變量與我們在前面介紹過的單純?nèi)肿兞渴怯袇^(qū)別的。全局靜態(tài)變量有一個特點,就是只有在定義它的程序文件中才可以使用它,其他文件不能改變其內(nèi)容。
C語言允許進行多模塊程序設(shè)計,一個較大型的程序可被分成若干個模塊,分別由幾個人來完成。如果各人在獨立設(shè)計各自的程序模塊時,有些變量可能只希望在自己的程序模塊文件中使用,而不希望被別的模塊文件引用,對于這種變量就可以定義為全局靜態(tài)變量。
需要指出的是,全局靜態(tài)變量和單純?nèi)肿兞慷际窃诰幾g時就已經(jīng)分配了固定的內(nèi)存空間的變量,只是它們的作用范圍不同而已。
對于函數(shù)也可以定義成具有靜態(tài)存儲種類的屬性。定義函數(shù)時在函數(shù)名前面冠以關(guān)鍵字static即將其定義為一個靜態(tài)函數(shù)。例如,static int func1(char x, int y)。使用靜態(tài)函數(shù)可使該函數(shù)只局限于其所在的模塊文件。由于函數(shù)都是外部型的,因此靜態(tài)外部函數(shù)定義就限制了該函數(shù)只能在定義它的模塊文件中使用,其他模塊文件是不能調(diào)用它的。換句話說,在其他模塊文件中可以定義與靜態(tài)函數(shù)完全同名的另一個函數(shù),分別編譯并連接成為一個可執(zhí)行程序之后,不會由于程序中存在相同的函數(shù)名而發(fā)生函數(shù)調(diào)用時的混亂。這一特點在進行模塊化程序設(shè)計時是十分有用的。
4.寄存器變量(register)
為了提高程序的執(zhí)行效率,C語言允許將一些使用頻率最高的那些變量,定義為能夠直接使用硬件寄存器的所謂寄存器變量。定義一個變量時在變量名前面冠以存儲種類符號“register”即將該變量定義成為了寄存器變量。寄存器變量可以被認為是自動變量的一種,它的有效作用范圍也與自動變量相同。
由于計算機中的寄存器是有限的,不能將所有變量都定義成寄存器變量。通常在程序中定義的寄存器變量時只是給編譯器一個建議,該變量是否能真正成為寄存器變量,要由編譯器根據(jù)實際情況來確定。另一方面,Cx51編譯器能夠識別程序中使用頻率最高的變量,在可能的情況下,即使程序中并未將該變量定義為寄存器變量,編譯器也會自動將其作為寄存器變量處理。下面來看一個帶有匯編碼的程序例子。
例3.11:使用寄存器變量的例子——計算以整數(shù)為底的指數(shù)的冪。
stmt level source 1 #include<stdio.h> 2 int_power(m, e) 3 int m; 4 register int e; 5 { 6 1 register int temp; 7 1 temp=1; 8 1 for (; e; e--) 9 1 temp*=m; 10 1 return(temp); 11 1 } 12 13 main() { 14 1 int x, y; 15 1 printf("please input X Y\n"); 16 1 scanf("%d %d", &x, &y); 17 1 printf("%d to the power of %d = %d",x,y,int_power(x, y)); 18 1 } 19 ASSEMBLY LISTING OF GENERATED OBJECT CODE ;FUNCTION _int_power (BEGIN) ;SOURCE LINE # 2 0000 8E00 R MOV m,R6 0002 8F00 R MOV m+01H,R7 ;---- Variable 'e' assigned to Register 'R2/R3' ---- 0004 AB05 MOV R3,AR5 0006 AA04 MOV R2,AR4 ;SOURCE LINE # 3 ;SOURCE LINE # 7 ;---- Variable 'temp' assigned to Register 'R6/R7' ---- 0008 7F01 MOV R7,#01H 000A 7E00 MOV R6,#00H ;SOURCE LINE # 8 000C ?C0001: 000C EB MOV A,R3 000D 4A ORL A,R2 000E 600E JZ ?C0002 ;SOURCE LINE # 9 0010 AC00 R MOV R4,m 0012 AD00 R MOV R5,m+01H 0014120000 E LCALL ?C?IMUL 0017 EB MOV A,R3 0018 1B DEC R3 0019 70F1 JNZ ?C0001 001B 1A DEC R2 001C ?C0006: 001C 80EE SJMP ?C0001 001E ?C0002: ;SOURCE LINE # 10 ;SOURCE LINE # 11 001E ?C0004: 001E 22 RET ;FUNCTION _int_power (END) ;FUNCTION main (BEGIN) ;SOURCE LINE # 13 ;SOURCE LINE # 15 0000 7BFF MOV R3,#0FFH 0002 7A00 R MOV R2,#HIGH ?SC_0 00047900 R MOV R1,#LOW ?SC_0 0006120000 E LCALL _printf ;SOURCE LINE # 16 0009750000 E MOV ?_scanf?BYTE+03H,#00H 000C 750000 R MOV ?_scanf?BYTE+04H,#HIGH x 000F 750000 R MOV ?_scanf?BYTE+05H,#LOW x 0012750000 E MOV ?_scanf?BYTE+06H,#00H 0015750000 R MOV ?_scanf?BYTE+07H,#HIGH y 0018750000 R MOV ?_scanf?BYTE+08H,#LOW y 001B 7BFF MOV R3,#0FFH 001D 7A00 R MOV R2,#HIGH ?SC_19 001F 7900 R MOV R1,#LOW ?SC_19 0021120000 E LCALL _scanf ;SOURCE LINE # 17 0024 AD00 R MOV R5,y+01H 0026 AC00 R MOV R4,y 0028 AF00 R MOV R7,x+01H 002A AE00 R MOV R6,x 002C 120000 R LCALL _int_power 002F 8E00 E MOV ?_printf?BYTE+07H,R6 0031 8F00 E MOV ?_printf?BYTE+08H,R7 0033 7BFF MOV R3,#0FFH 0035 7A00 R MOV R2,#HIGH ?SC_26 00377900 R MOV R1,#LOW ?SC_26 0039850000 E MOV ?_printf?BYTE+03H,x 003C 850000 E MOV ?_printf?BYTE+04H,x+01H 003F 850000 E MOV ?_printf?BYTE+05H,y 0042850000 E MOV ?_printf?BYTE+06H,y+01H 0045020000 E LJMP _printf ;FUNCTION main (END)
程序執(zhí)行結(jié)果:
please input X Y 5 3 回車 5 to the power of 3 = 125
在這個程序中定義了一個計算以整數(shù)為底的指數(shù)冪的函數(shù)int_power(),該函數(shù)中有兩個形式參數(shù)int m和register int e。它們都是int類型的變量,但參數(shù)e前面帶有存儲種類說明符register,被特別說明為寄存器變量。另外,在該函數(shù)體中還定義了一個int類型的寄存器變量register int temp。從編譯得到的匯編碼可以看到,形式參數(shù)e被分配給了8051單片機的工作寄存器R2和R3,但變量temp則未分配到工作寄存器,而是作為臨時工作單元被存放到內(nèi)存之中。由此可見,盡管可以在程序中定義寄存器變量,但實際上被定義的變量是否真能成為寄存器變量最終是由編譯器決定的。
3.5.3 函數(shù)的參數(shù)和局部變量的存儲器模式
Keil Cx51編譯器允許采用三種存儲器模式:small、compact和large。一個函數(shù)的存儲器模式確定了函數(shù)的參數(shù)和局部變量在內(nèi)存中的地址空間。處于small模式下函數(shù)的參數(shù)和局部變量位于8051單片機的內(nèi)部RAM中,處于compact和large模式下函數(shù)的參數(shù)和局部變量則使用8051單片機的外部RAM。在定義一個函數(shù)時可以明確指定該函數(shù)的存儲器模式,一般形式為:
函數(shù)類型 函數(shù)名(形式參數(shù)表)[存儲器模式]
其中,“存儲器模式”是Keil Cx51編譯器擴展的一個選項。不用該選項時即沒有明確指定函數(shù)的存儲器模式,這時該函數(shù)按編譯時的默認存儲器模式處理。
例3.12:函數(shù)的存儲器模式。
#pragma large /* 默認存儲器模式為LARGE */ extern int calc(char i, int b) small; /* 指定SMALL模式 */ extern int func(int i, float f) large; /* 指定LARGE模式 */ extern void * tcp(char xdata *xp, int ndx) small; /* 指定SMALL模式 */ int mtest(int i, int y) small /* 指定SMALL模式 */ { return(i*y+y*i+func(-1, 4.75))' } int large_func(int i, int k) /* 未指定模式,按默認的LARGE模式處理 */ { return(mtest(i,k)+2); }
這個例子程序的第一行用了一個預(yù)編譯命令“#pragma”,它的意思是告訴Keil Cx51編譯器在對程序進行編譯時,按該預(yù)編譯命令后面給出的編譯控制指令“l(fā)arge”進行編譯,即本例程序編譯時的默認存儲器模式為large。程序中一共有五個函數(shù):calc()、func()、*tcp()、mtest()和large_func(),其中前面四個函數(shù)都在定義時明確指定了其存儲器模式,只有最后一個函數(shù)未指定。在用Cx51進行編譯時,只有最后一個函數(shù)按large存儲器模式處理,其余四個函數(shù)則分別按它們各自指定的存儲器模式處理。
這個例子說明,Keil Cx51編譯器允許采用所謂存儲器的混合模式,即允許在一個程序中某個(或幾個)函數(shù)使用一種存儲器模式,另一個(或幾個)函數(shù)使用另一種存儲器模式。采用存儲器混合模式編程,可以充分利用8051系列單片機中有限的存儲器空間,同時還可加快程序的執(zhí)行速度。
- Mastering Entity Framework Core 2.0
- Learning ROS for Robotics Programming(Second Edition)
- C語言程序設(shè)計(第2 版)
- MATLAB圖像處理超級學(xué)習(xí)手冊
- 機器人Python青少年編程開發(fā)實例
- Learning Concurrent Programming in Scala
- 深入分布式緩存:從原理到實踐
- Java系統(tǒng)化項目開發(fā)教程
- ExtJS高級程序設(shè)計
- ServiceNow:Building Powerful Workflows
- Geospatial Development By Example with Python
- Instant Debian:Build a Web Server
- Xcode 6 Essentials
- 你好!Java
- Getting Started with Web Components