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