51單片機基礎剖析(基于C語言)
在編寫應用程序時,定義一個變量,一個數(shù)組,或是說一個固定表格,到底存儲在什么地方;當定義變量大小超過MCU的內(nèi)存范圍時怎么辦;如何控制變量定義不超過存儲范圍;
本文引用地址:http://m.butianyuan.cn/article/201611/318636.htm以及如何定義變量才能使得變量訪問速度最快,寫出的程序運行效率最高。以下將一一解答。
1.六類存儲類型 code data idata xdata pdata bdata
code:程序存儲器,也即只讀存儲器,用來保存常量或是程序,采用16位地址線編碼,可以是在片內(nèi),或是片外,大小被限制在64KB。
作用:定義常量,如八段數(shù)碼表或是編程使用的常,在定義時加上code或明確指明定義的常量保存到code memory(只讀。)比如:
char code table[]={0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,0xf8,0x80,0x90};
此關鍵字的使用方法等同于const。
data:數(shù)據(jù)存儲區(qū),只能用于聲明變量,不能用來聲明函數(shù),該區(qū)域位于片內(nèi),采用8位地址線編碼,具有最快的存儲速度,但是數(shù)量被限制在128byte或更少。
使用方法:unsigned char data fast_variable=0;
Idata:數(shù)據(jù)存儲區(qū),只能用于聲明變量,不能用來聲明函數(shù)。該區(qū)域位于片內(nèi),采用8位地址線編碼,內(nèi)存大小被限制在256byte或更少。該區(qū)域的低地址區(qū)與data區(qū)地址一致,高地址區(qū)域是52系列在51系列基礎上擴展的并與特殊功能寄存器具有相同地址編碼的區(qū)域。即:data memory是idata memory的一個子集。
xdata:只能用于聲明變量,不能用來聲明函數(shù),該區(qū)域位于MCU外部,采用16位地址線進行編碼,存儲大小被限制在64KB以內(nèi)。如:unsigned char xdata count=0;
pdata:只能用于聲明變量,不能用來聲明函數(shù),該區(qū)域位于MCU外部,采用8位地址線進行編碼。存儲大小限制在256byte,是xdata memory的低256byte。為其子集。如:unsigned char pdata count=0;
bdata:只能用于聲明變量,不能用來聲明函數(shù)。該區(qū)域位于8051內(nèi)部位數(shù)據(jù)地址。定義的量保存在內(nèi)部位地址空間,可用位指令直接讀寫。使用方法:unsigned char bdata varab=0。
注:一般情況下,定義字符型變量時,在缺省unsigned的情況下,默認為無符號。但是本人在Keil uV3中遇到并非如此的案例。在缺省的情況下默認為有符號。要注意一下,或許不同的編譯器規(guī)則不同。所以我們在寫程序的時候,還是最好把unsigned signed加上。
2.函數(shù)的參數(shù)和局部變量的存儲模式
C51 編譯器允許采用三種存儲器模式:SMALL,COMPACT 和LARGE。一個函數(shù)的存儲器模式確定了函數(shù)的參數(shù)的局部變量在內(nèi)存中的地址空間。處于SMALL模式下的函數(shù)參數(shù)和局部變量位于8051單片機內(nèi)部RAM中,處于COMPACT和LARGE模式下的函數(shù)參數(shù)和局部變量則使用單片機外部RAM。在定義一個函數(shù)時可以明確指定該函數(shù)的存儲器模式。方法是在形參表列的后面加上一存儲模式。
示例如下:
#pragma large //此預編譯必須放在所有頭文前面
int func0(char x,y) small;
char func1(int x) large;
int func2(char x);
注:上面例子在第一行用了一個預編譯命令#pragma,它的意思是告訴c51編譯器在對程序進行編譯時,按該預編譯命令后面給出的編譯控制指令LARGE進行編譯,即本例程序編譯時的默認存儲模式為LARGE。隨后定義了三個函數(shù),第一個定義為SMALL存儲模式,第二個函數(shù)定義為LARGE第三個函數(shù)未指定,在用C51進行編譯時,只有最后一個函數(shù)按LARGE存儲器模式處理,其它則分別按它們各自指定的存儲器模式處理。
本例說明,C51編譯器允許采用所謂的存儲器混合模式,即允許在一個程序中將一些函數(shù)使用一種存儲模式,而其它一些則按另一種存儲器模式,采用存儲器混合模式編程,可以充分利用8051系列單片機中有限的存儲器空間,同時還可以加快程序的執(zhí)行速度。
3.絕對地址訪問(頭文件為:absacc.h(相當重要))
#define CBYTE ((unsigned char volatile code *) 0)
#define DBYTE ((unsigned char volatile data *) 0)
#define PBYTE ((unsigned char volatile pdata *) 0)
#define XBYTE ((unsigned char volatile xdata *) 0)
功能:CBYTE尋址CODE區(qū)
DBYTE尋址DATA區(qū)
PBYTE尋址XDATA(低256)區(qū)
XBYTE尋址XDATA區(qū)
例:如下指令在對外部存儲器區(qū)域訪問地址0x1000
xvar=XBYTE[0x1000];
XBYTE[0x1000]=20;
#define CWORD ((unsigned int volatile code *) 0)
#define DWORD ((unsigned int volatile data *) 0)
#define PWORD ((unsigned int volatile pdata *) 0)
#define XWORD ((unsigned int volatile xdata *) 0)
功能:與前面的一個宏相似,只是它們指定的數(shù)據(jù)類型為unsigned int。
通過靈活運用不同的數(shù)據(jù)類型,所有的8051地址空間都是可以進行訪問。例如:
DWORD[0x0004]=0x12F8;// 即內(nèi)部數(shù)據(jù)存儲器中(0x08)=0x12; (0x09)=0xF8
注:用以上八個函數(shù),可以完成對單片機內(nèi)部任意ROM和RAM進行訪問,非常方便。還有一種方法,那就是用指鐘,后面會對C51的指針有詳細的介紹。
4.寄存器變量(register)
為了提高程序的執(zhí)行效率,C語言允許將一些頻率最高的那些變量,定義為能夠直接使用硬件寄存器的所謂的寄存器變量。定義一個變量時,在變量類型名前冠以“register” 即將該變量定義成為了寄存器變量。寄存器變量可以認為是一自動變量的一種。有效作用范圍也自動變量相同。由于計算機寄存器中寄存器是有限的。不能將所有變量都定義成為寄存器變量,通常在程序中定義寄存器變量時,只是給編譯器一個建議,該變量是否真正成為寄存器變量,要由編譯器根據(jù)實際情況來確定。另一方面,C51編譯器能夠識別程序中使用頻率最高的變量,在可能的情況下,即使程序中并未將該變量定義為寄存器變量,編譯器也會自動將其作為寄存器變量處理。被定義的變量是否真正能成為寄存器變量,最終是由編譯器決定的。
5.內(nèi)存訪問的實現(xiàn)
(1)指鐘
指鐘本身是一個變量,其中存放的內(nèi)容是變量的地址,也即特定的數(shù)據(jù)。8051的地址是16位的,所以指針變量本身占用兩個存儲單元。指針的說明與變量的說明類似,僅在指針名前加上“*”即可。
如: int *int_point; //聲明一個整型指針
char *char_point; //聲明一個字符型指針
利用指針可以間接存取變量。實現(xiàn)這一點要用到兩個特殊運算符
& 取變量地址
* 取指針指向單元的數(shù)據(jù)
示例一:
int a=15,b;
int *int_point; //定義一個指向整型變量的指針
int_point=&a; //int_point指向 a
*int_point=5; //給int_point指向的變量a 賦值5 等同于a=5;
示例二:
char i,table[6],*char_point;
char_point=table;
for(i=0;i<6;i++)
{
char_point=i;
char_point++;
}
注:指針可以進行運算,它可以與整數(shù)進行加減運算(移動指針)。但要注意,移動指針后,其地址的增減量是隨指針類型而異的,如,浮點指針進行自增后,其內(nèi)部將在原有的基礎上加4,而字符指針當進生自增的時候,其內(nèi)容將加1。原因是浮點數(shù),占4個內(nèi)存單元,而字符占一個字節(jié)。
宏晶科技最新一代STC12C5A360S2系列,每一個單片機出廠時都有全球唯一身份證號碼(ID號),用戶可以在單片機上電后讀取內(nèi)部RAM單元F1H~F7H的數(shù)值,來獲取此單片機的唯一身份證號碼。使用MOV @Ri指令來讀取。下面介紹C51獲取方法:
char id[7]={0};
char i;
char idata *point;
for(i=0;i<7;i++)
{
id[i]=*point;
point++;
}
(此處只是對指針做一個小的介紹,達到訪問內(nèi)部任何空間的方式,后述有對指針使用的詳細介紹)
(2)對SFR,RAM ,ROM的直接存取
C51提供了一組可以直接對其操作的擴展函數(shù)
若源程序中,用#include包含頭文件,io51.h 后,就可以在擴展函數(shù)中使用特殊功能寄存器的地址名,以增強程序的可讀性:
注 此方法對SFR,RAM,ROM的直接存取不建議使用.因為,淡io51.h這個頭文件在KEIL中無法打開,可用指針,或是采用absacc.h頭文件,
(3) PWM與PCA
STC12系列有兩路PWM/PCA
PWM:(Pulse Width Modulation)脈寬調(diào)制,是一種使用程序來控制波形占空比,周期,相位波形的技術。
PCA:(Programmable Counter Array)可編程計數(shù)陣列,它比通常的定時/計數(shù)器的定時能力強,需要CPU的干預少。其優(yōu)勢一是軟件簡單,二是精度大有提高。
*6.動態(tài)內(nèi)存分配的實現(xiàn)
在單片機的實際開發(fā)中,很多情況下我么需要開辟一塊內(nèi)存,但是具體開辟多大,也就是內(nèi)存的字節(jié)數(shù)我們還無法確定,比如可能要等到上位機的指令發(fā)送下來才能確定,這個時候我們就得動態(tài)分配內(nèi)存。注意,單片機內(nèi)部存儲資源是極其有限的,不允許開發(fā)人員開辟出一塊很大的存儲區(qū)來備用。在VC 6.0環(huán)境下很容易用malloc()來得到一塊RAM,但是由于單片機內(nèi)部沒有操作系統(tǒng)(如何在51上跑uC/OS-II我以后會寫出來),所以在51上實現(xiàn)動態(tài)內(nèi)存分配就是個難點也是一個重點問題。下面給出代碼,詳細分析大家可以參考求是科技編的《8051系列單片機C程序設計完全手冊》這本書。
#include
#include
……
void main (void)
{
char *ptr1;
init_mempool (0x1000,0x500); //內(nèi)存池初始化,0x1000為起始地址,0x500為內(nèi)存大小
ptr1=malloc(30); /*動態(tài)為指針變量分配長度為30字節(jié)的存儲空間*/
……
//此處為你的代碼
……
free(ptr1) ; //注意,動態(tài)內(nèi)存用完之后務必要釋放,否則程序?qū)鲥e
while (1);
}
二、變量類型及其作用域剖析
變量可分為 1.局部變量;2.全局變量(按變量的有效作用范圍劃分)
1.局部變量
是指函數(shù)內(nèi)部(包括main函數(shù))定義的變量,僅在定義它的那個函數(shù)范圍內(nèi)有效,不同函數(shù)可使用相同的局部變量名,函數(shù)的形式參數(shù)也屬于局部變量,在一個函數(shù)的內(nèi)部復合語句中也可以定義局部變量,該局部變量只在該復合語合中有效。
2.全局變量
是指函數(shù)外部定義的變量,以稱外部變量??蔀槎鄠€函數(shù)共同使用,其有效作用范圍是從它定義開始到整個程序文件結(jié)束。如果全局變量,定義在一個程序文件的開始處,則在整個程序文件范圍都可以使用它,如果一個全局變量不是在程序文件的開始處定義,但又希望在它定義之前的函數(shù)中引用該變量,這時應在引用該變量的函數(shù)中用關鍵字extern將其聲明為“外部變量”。另個,如果在一個程序模塊文件中引用另一個程序模塊文件中定義的變量時,也必須用extern進行說明。
外部變量的說明與外部變量的定義是不同的,外部變量定義只能有一次,定義的位置在所有函數(shù)之外,而同一個程序文件中(不是指模塊文件)的外部變量聲明可以有多次,聲明的置在需要引用該變量的函數(shù)之內(nèi),外部變量的聲明的作用只是聲明該變量是一個已經(jīng)在外部定義過了的變量而已。
如在同一個程序文件中,全局變量與局部變量同名,則在局部變量的有效作用范圍之內(nèi),全局變量不起作用,也就是說,局部變量的優(yōu)先級比全局變量高。
在編寫C語言程序時,不是特別必要的地方一般不要使用全局變量,而應當盡可能的使用局部變量。因為局部變量只在使用它的時候,才為其分配內(nèi)存單元,而全局變量在整個程序的執(zhí)行過程中都要占用內(nèi)存單元,且當全局變量使用過多時,會降低程序的可讀性。
變量的存儲種類
(1).自動變量(auto)
定義變量時,在變量類型名前加上 “auto” ,自動變量是C語言中使用最為廣泛的一類變量,在函數(shù)體內(nèi)部或是復合語句內(nèi)部定義的變量,如果省略了存儲種類說明,則該變量默認為自動變量。
例如:
{ 等價于 {
char x; auto char x;
int y; auto int y;
…… ……
} }
注:自動變量的作用范圍在定義它的函數(shù)體或是復合語句內(nèi)部,只有在定義它的函數(shù)內(nèi)被調(diào)用,或是定義它的復合語句被執(zhí)行時,編譯器才會為其分配內(nèi)存空間,開始其生存期。當函數(shù)調(diào)用結(jié)束返回,或復合語句執(zhí)行結(jié)束,自動變量所占用的內(nèi)存空間就被釋放,變量的值當然也就不復存在,其生存期結(jié)束。當函數(shù)再次調(diào)用,或是復合語句被再次執(zhí)行時,編譯器又會為其內(nèi)部的自動變量重新分配內(nèi)存空間。但不會保留上一次運行的值。而必須被重新分配。因此自動變量始終是相對于函數(shù)或復合語句的局部變量。
(2).外部變量(extern)
用說明符“extern”定義的變量稱為外部變量。按缺省規(guī)則,凡是在所有函數(shù)之前,在函數(shù)外部定義的變量都是外部變量,定義時可以不寫extern說明符,但是一個函數(shù)體內(nèi)說明一個已在該函數(shù)體外或別的程序模塊文件中定義過的外部變量時,剛必須要使用extern說明符。外部變量定義后,它就被分配了固定的內(nèi)存空間。外部變量的生存期為程序的整個執(zhí)行時間。 外部變量的存儲不會隨函數(shù)或復合語句執(zhí)行完畢而釋放,因此外部變量屬于全局變量。
C語言允許將大型程序分解為若干個獨立的程序模塊文件,各個模塊可分別進行編譯,然后再將它們連接在一起,如果某個變量需要在所有程序模塊文件中使用,只要在一個程序模塊文件中將該變量定義成全局變量,而在其它程序模塊文件中用extern聲明該變量是已被定義過的外部變量就可以了。
函數(shù)是可以相互調(diào)用的,定義函數(shù)時,如果冠以關鍵字extern 即將其明確定義為一個外部函數(shù)。例如 extern int func2(char a,b) 。如果在定義函數(shù)時省略關鍵字extern,則隱含為外部函數(shù)。如果在調(diào)用一個在本程序模塊文件以外的其它模塊文件所定義的函數(shù),則必須要用關鍵字extern說明被調(diào)用的函數(shù)是一個外部函數(shù)。對于具有外部函數(shù)相互調(diào)用的多模塊程序,可用C51編譯器分別對各個模塊文件進行編譯,最后再用L51連接定位器將它們連接成為一個完整的程序。如下為一個多模塊程序:
程序模塊1,文件名為file1.c
#include
int x=5;
void main()
{
extern void fun1( );
extern viod fun2(int y);
fun1( );
fun1( );
fun1( );
printf( “n%d %dn”,x,fun2(x));
}
程序模塊2,文件名為file2.c
#include
extern int x;
void fun1( )
{
static int a=5; //靜態(tài)變量只在第一次調(diào)用函數(shù)時賦值,退出函數(shù)時
//會保留上次的值,下次調(diào)用不再重新賦值。
int b=5;
printf(“%d %d %d |”,a,b,x);
a-=2;
b-=2
x-=2;
printf(“%d %d %d |”,a,b,x);
}
int fun2(int y)
{
return(35*x*y);
}
程序執(zhí)行如果如下:
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)肿兞康闹?,有時還會發(fā)生一些意想不到的副作用。因些最好還是使用函數(shù)參數(shù)來傳遞數(shù)據(jù)。
(3).寄存器變量(register)
為了提高程序的執(zhí)行效率,C語言允許將一些頻率最高的那些變量,定義為能夠直接使用硬件寄存器的所謂的寄存器變量。定義一個變量時,在變量類型名前冠以“register” 即將該變量定義成為了寄存器變量。寄存器變量可以認為是一自動變量的一種。有效作用范圍也自動變量相同。由于計算機寄存器中寄存器是有限的。不能將所有變量都定義成為寄存器變量,通常在程序中定義寄存器變量時,只是給編譯器一個建議,該變量是否真正成為寄存器變量,要由編譯器根據(jù)實際情況來確定。另一方面,C51編譯器能夠識別程序中使用頻率最高的變量,在可能的情況下,即使程序中并未將該變量定義為寄存器變量,編譯器也會自動將其作為寄存器變量處理。被定義的變量是否真正能成為寄存器變量,最終是由編譯器決定的。
(4).靜態(tài)變量(static)
使用存儲種類說明符“static”定義的變量為靜態(tài)變量,在上面模塊2程序文件中使用了一個靜態(tài)變量:static int a =5 ;由于這個變量是在函數(shù)fun1( )內(nèi)部定義,因此稱為內(nèi)部靜態(tài)變量或局部靜態(tài)變量。局部靜態(tài)變量始終都是存在的,但只有在定義它的函數(shù)內(nèi)部進行訪問,退出函數(shù)之后,變量的值仍然保持,但不能進行訪問。
還有一種全局靜態(tài)變量,它是在函數(shù)外部被定義的。作用范圍從它的定義點開始,一直到程序結(jié)束,當一個C語言程序由若干個模塊文件所組成時,全局靜態(tài)變量始終存在,但它只能在被定義的模塊文件中訪問,其數(shù)據(jù)值可為該模塊文件內(nèi)的所有函數(shù)共享,退出該文件后,雖然變量的值仍然保持著,但不能被其它模塊文件訪問。在一個較大的程序中,這就方便了多人設計時,各自寫的程序模塊不會被別的模塊文件所引用。全局靜態(tài)變量和單純的全局變量,在編譯時就已經(jīng)為期分配了固定的內(nèi)存空間,只是他們的作用范圍不同而已。局部靜態(tài)變量是一種在兩次函數(shù)調(diào)用之間仍能保持其值的局部變量。如下,局部變量的使用——計算度輸出1~5的階乘值。
#include
int fac( int n)
{
static int f=1;
f=f*n;
return(f);
}
main( )
{
int i;
for(i=1;i<=5;i++)
printf(“%d!=%dn”,i,fac(i));
}
程序執(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)存空間,而且降低了程序的可讀性,因此并不建議多用局部靜態(tài)變量。
靜態(tài)函數(shù):
對于函數(shù)也可以定義成為具為靜態(tài)存儲種類的屬性,定義函數(shù)時在函數(shù)名前冠以關鍵字static即將其定義為一個靜態(tài)函數(shù)。例如static int func1(char x, y)函數(shù)是外部型的,使用靜態(tài)函數(shù)可以使該函數(shù)只局限于當前定義它的模塊文件中。其它模塊文件是不能調(diào)用它的。換名話說,就是在其它模塊文件中可以定義與靜態(tài)函數(shù)完全同名的另一個函數(shù)。不會因為程序中存在相同的函數(shù)名而發(fā)生函數(shù)調(diào)用時的混亂。 這一點對于進行模塊化程序設計是很有用的。
三、中斷淺談
0 | 外中斷0 |
1 | 定時器0 |
2 | 外中斷1 |
3 | 定時器1 |
4 | 串行口 |
定義中斷函數(shù)如下
void timer1() interrupt 3
{
……
……
}
強烈建議:如上所述,定義中斷函數(shù)時不要加using n選項。除非你對你的程序以及單片機的工作過程非常熟悉,否則會帶來不必要的麻煩。具體原因由于篇幅的限制暫不討論。
C51中斷程序編寫要求:
1.中斷函數(shù)不能進行參數(shù)傳遞,否則,將導致編譯出錯
2.中斷中,不能包含任何參數(shù)聲明,否則,將導致編譯出錯。
3.中斷函數(shù)沒有返回值,如果企圖定義一個返回值將得到不正確的結(jié)果,因些建議在定義中斷函數(shù)的時將其定義為void 類型,明確說明沒有返回值。
4.任何情況下都不能直接調(diào)用中斷函數(shù),否則會主生編譯出錯。
5.如果中斷函數(shù)中用到了浮點運算,必須保存浮點寄存器的狀態(tài)。當沒有其它的程序執(zhí)行浮點運算時(即只有中斷中用到浮點運算),可以不用保存。
6.如果中斷函數(shù)中調(diào)用了其它函數(shù),則被調(diào)用的函數(shù)所使用的寄存器組必須與中斷函數(shù)相同,用戶必須保證按要求使用相同的寄存器組,否則會產(chǎn)生不正確的結(jié)果,這一點必須引起足夠的注意,如果定義中斷函數(shù)時沒有使用using選項,則由編譯器選擇一個寄存器組作絕對寄存器訪問。另外,不斷的產(chǎn)生不可預測,中斷函數(shù)對其它函數(shù)的調(diào)用可能形成遞規(guī)調(diào)用,需要時,可將被中斷調(diào)用的其它函數(shù)定義為再入函數(shù)。
淺析函數(shù)的遞規(guī)調(diào)用與再入函數(shù):
函數(shù)的遞規(guī)調(diào)用: 在調(diào)用一個函數(shù)的過程中雙直接或間接的調(diào)用該函數(shù)本身;
再入函數(shù):一種可以在函數(shù)體內(nèi)直接或間接調(diào)用其自身的一種函數(shù)。
C51編譯器采用一個擴展關鍵字reentrant 作為定義函數(shù)時的選項,需要將一個函數(shù)定義為再入函數(shù)時,只要在函數(shù)名后加上關鍵字reentrant即可??詹豢崭褚约翱諑赘穸紵o所謂。
再入函數(shù)剖析:
再入函數(shù)可被遞歸調(diào)用,無論何時,包括中斷服務函數(shù)在內(nèi)的任何函數(shù)都可調(diào)用再入函數(shù)。與非再入函數(shù)的參數(shù)傳遞和局部就是的存儲分配方法不同,C51編譯器為每個再入函數(shù)都生成一個模擬棧。模擬棧所在的存儲器空間根據(jù)再入函數(shù)的存儲模式的不同,可以分配到DATA,PDATA 或XDATA。
對再入函數(shù)有如下規(guī)定:
1.再入函數(shù)不能傳送bit類型的參數(shù)。也不能定義一個局部位變量,再入函數(shù)不能包括位操作以及8051系列單片機的可位尋址區(qū)。
2.與PL/ M51兼容的函數(shù),不能具有reentrant屬性,也不能調(diào)用再入函數(shù)。
3.編譯時,在存儲器模式的基礎上,為再入函數(shù)在內(nèi)部或外部存儲中建立一個模擬堆棧區(qū),稱為再入棧,再入函數(shù)的局部變量及參數(shù)被放在再入棧中,從而使得再入函數(shù)可以進行遞規(guī)調(diào)用。再非再入函數(shù)的局部變量被放在再入棧之外的暫存區(qū)內(nèi),如果對非再入函數(shù)進行遞規(guī)調(diào)用,則上次調(diào)用時使用的局部變量數(shù)據(jù)將被覆蓋。
4.在同一個程序中可以定義和使用不同存儲器模式的再入函數(shù),任意模式的再入函數(shù)不能調(diào)用不同模式的再入函數(shù),但可以任意調(diào)用非再入函數(shù)。
5.在參數(shù)的傳遞上,實際參數(shù),可以傳遞給間接調(diào)用的再入函數(shù),無再入屬性的間接調(diào)用函數(shù)不能包含調(diào)用參數(shù)。但是可以使用定義的全局變量來進行參數(shù)傳遞。
四、C51指針深度剖析(非常重要,嵌入式系統(tǒng)開發(fā)人員必須要掌握的內(nèi)容)
注意:由于篇幅所限,本人暫時不打算討論抽象指針的內(nèi)容。但是你必須上網(wǎng)或去圖書館找找關于抽象指針的資料好好看看,抽象指針很有用的。
指針是C語言中的一個重要概念,使用也十分普遍,正確使用指針類型數(shù)據(jù)可以有效的表示復雜的數(shù)據(jù)結(jié)構(gòu),直接處理內(nèi)存地址,而且可以更為有效的使用數(shù)組。
在C語言中,為了能夠?qū)崿F(xiàn)直接對內(nèi)存單元的操作,引入了指針類型的數(shù)據(jù),指針類型數(shù)據(jù)是專門用來確定其它數(shù)據(jù)類型的地址的,因此一個變量的地址就被稱為該變量的指針如: 一個整形變量i 存放在內(nèi)存單元40H中,則該內(nèi)存單元地址40H就是變量i 的指針。如果有一個變量專門用來存放另一個變量的地址,則稱之為“指針變量”
變量指針與指針變量
變量的指針: 是指某個變量的地址,而一個指針變量里面存放的是另一個變量在內(nèi)存中的地址。擁有這個地址的變量則稱為該指針變量所指向的變量。 所以每個變量都有它自己的指針(地址),而每一個指針變量都是指向另一個變量的。C語言中用符號“*”來表示“指向”,如下:
i=50;
*ip=50;
如果指針ip這個指針變量指向i那么,兩個賦值表達或同義,第二個表達式可以解釋為“給指針變量ip所指向的變量賦值50”。
(1).指針變量的定義
指針變量的定義與一般變量的定義類似,其一般形式如下:
數(shù)據(jù)類型 [存儲器類型] * 標識符;
標識符, 是所定義的指針變量名
數(shù)據(jù)類型, 說明了該指針變量所指向的變量類型
存儲器類型,是可選的,它是C51編譯器的一種擴展,如果帶有此選項,指針被定義為基于存儲器的指針,無此選項時,被定義為一般指針,這兩種指針的區(qū)別在于它們的存儲字節(jié)不同。
一般指針:占用三個字節(jié),第一個字節(jié)存放該指針存儲器類型的編碼,第二和第三個字節(jié)分別存放該指針的高位和低位地址的偏移量
存儲器類型 | IDATA | XDATA | PDATA | DATA | CODE |
編碼值 | 1 | 2 | 3 | 4 | 5 |
基于存儲器指針:則該指針長度可為一個字節(jié),也可為兩字節(jié)
一個字節(jié): (存儲器類型 idata data pdata)
兩個字節(jié): (存儲器類型為code xdata)
注:在定義指針變量時最好指定其為基于存儲器的指針,這個生成的匯編代碼長精 練一些,而且也節(jié)省空間(讀者可自行到C51中寫一個程序,查看其反匯編程序)但在一些函數(shù)調(diào)用的參數(shù)中指針需要采用一般指針,為此C51編譯器允許這兩種指針相互轉(zhuǎn)換,轉(zhuǎn)換規(guī)則如下:
一般指針轉(zhuǎn)換成基于存儲器指針,采取截斷,基于存儲器類型指針轉(zhuǎn)換成一般指針采用擴展的。
(2).指針變量的引用
指針變量是含有一個數(shù)據(jù)對象地址的特殊變量,指針變量中只能存放地址與指針變量有關的兩個運算符:
& 取地址運算符
* 間接訪問運算符
&a為取變量a的地址,*P為指針變量P所指向的變量。如下:
int i , x, y;
int *pi,*px,*py;
pi=&i; //將變量i的地址賦給指針變量pi,也即pi指向i
px=&x;
py=&py;
*pi=0; //等價于i=0
*px+=6; //等價于 i+=6
(*py)++; //等價于 i++
注:指向同類數(shù)據(jù)的指針之間可以相互賦值。如pi=px;
(3).指針變量作為函數(shù)的參數(shù)
函數(shù)的參數(shù)不僅可以是整型,字符型等數(shù)據(jù),還可以是指針類型,指針變量作為函數(shù)的參數(shù)的作用是將一個變量的地址傳到另一個函數(shù)中去,地址傳遞是雙向的,即主調(diào)用函數(shù)不僅可以向被調(diào)用函數(shù)傳遞參數(shù),而且還可以從被調(diào)用函數(shù)返回其結(jié)果。下面通過一個簡單的示例來進行說明。
#include
swap(int *pi,int *pj)
{
int temp;
temp=*pi;
*pi=*pj; //把指針變量pj所指向的變量的值送給pi所指向的變量
*pj=temp;
}
main( )
{
int a,b;
int *pa, *pb;
a=9;
b=7;
pa=&a;
pb=&b;
if(a
printf(“n max=%d,min=%d n”,a,b);
}
上程序上定義了一個swap( )函數(shù),兩個形參為指針變量,在調(diào)用函數(shù)時,所用的實參也是指針變量,在調(diào)用開始,實參變量將它的值傳遞給形參變量,采取的仍然是“值傳遞”方式,但這時傳遞的是指針的值(地址),傳遞后,形參pi的值為&a,pj的值為&b,即指針變量*pi 和*pa都指向了a, *pj和*pb指向了b。接著使*pj與*pi的值互換,從而達到了實現(xiàn)了a,和b值的互換。雖然函數(shù)返回時,pi pj被釋放而不存在,但main函數(shù)中a 與b的值已經(jīng)交換。
(4).數(shù)組的指針
在C語言中,指針與數(shù)組有著十分密切的關系,任何能夠用數(shù)組實現(xiàn)的運算都可以通過指針來完成,例如定義一個具有十個元素的整形數(shù)據(jù)可以寫成:
int a[10];
數(shù)組名a表示元素a[0]的地址,而*a 則表示a所代表地址中的內(nèi)容,即a[0].
如果定義一個指向整形變量的指針pa并賦以數(shù)組a中的第一個元素a[0]的地址;
int *pa;
pa=&a[0]; //也可寫成pa=a;
則可通過指針pa來操作數(shù)組a了,即可用*pa代表a[0];*(pa+i)代表a[i],也可以上pa[0];pa[1];pa[2]……pa[9]的形式
(5).字符數(shù)組的指針
用指針來描述一個字符數(shù)組是十分方便的,字符串是以字符數(shù)組的形式給出的,并且每個字符數(shù)組都是以轉(zhuǎn)義字符‘