|
本帖最后由 piaolin 于 2015-10-30 14:02 編輯
通常認(rèn)為,C語(yǔ)言之所以強(qiáng)大,以及其自由性,很大部分體現(xiàn)在其靈活的指針運(yùn)用上,甚至認(rèn)為指針是C語(yǔ)言的靈魂。這里說(shuō)通常,是廣義上的,因?yàn)殡S著編程語(yǔ)言的發(fā)展,指針也飽受爭(zhēng)議,并不是所有人都承認(rèn)指針的“強(qiáng)大”和“優(yōu)點(diǎn)”。在單片機(jī)領(lǐng)域,指針同樣有著應(yīng)用,本章節(jié)針對(duì)Keil C-51環(huán)境下的指針意義做簡(jiǎn)要分析。
1 指針與變量
指針是一個(gè)變量,它與其他變量一樣,都是RAM中的一個(gè)區(qū)域,且都可以被賦值,如程序①所示。
#include "REG52.H"
unsigned int j;
unsigned char *p;
void main()
{
while(1)
{
j=0xabcd;
p=0xaa;
}
}
在Debug Session模式下,將鼠標(biāo)指針移到到變量“j”“p”位置,可以顯示變量的物理地址,如圖1-1、1-2所示。


圖中箭頭所指處即為變量在RAM中的“首地址”,為什么是“首地址”呢?變量根據(jù)類(lèi)型可分為8位(單字節(jié))、16位(雙字節(jié)),程序中變量“j”是無(wú)符號(hào)整型,所占物理空間應(yīng)為2字節(jié),而在8位單片機(jī)中,RAM的一個(gè)存儲(chǔ)單元大小是8位,即1字節(jié),因此需2個(gè)存儲(chǔ)單元才滿(mǎn)足變量“j”長(zhǎng)度。所以實(shí)際上變量“j”的物理地址為“08H”“09H”。同理,“p(D:0x0A)”即變量“p”的首地址為“0AH”。
下面通過(guò)單步執(zhí)行程序來(lái)觀察RAM內(nèi)的數(shù)據(jù)變化,打開(kāi)兩個(gè)Memory Windows窗口,在Keil軟件下方顯示為Memory1和Memory2,在兩個(gè)窗口中,分別做如圖2-1、2-2所示的設(shè)置。


兩個(gè)Address填寫(xiě)的內(nèi)容分別是:D:0x08、D:0x0A,即變量“j”和變量“p”的首地址,輸入后回車(chē),便可監(jiān)視RAM中該地址下的數(shù)據(jù)。設(shè)置好后,準(zhǔn)備調(diào)試。
在Debug Session模式中,箭頭所指處即為即將執(zhí)行的語(yǔ)句,單擊“Step”功能按鈕(或按F11鍵),讓程序運(yùn)行,如圖3所示。

第一次單擊“Step”按鈕后,Memory1窗口內(nèi)數(shù)據(jù)如圖4所示。

由調(diào)試結(jié)果可知,08H數(shù)據(jù)由00H變?yōu)?/font>ABH,09H數(shù)據(jù)由00H變?yōu)?/font>CDH,出現(xiàn)這種變化是因?yàn)閳?zhí)行了語(yǔ)句j=0xabcd;08H為變量“j”高八位,存儲(chǔ)“AB”,09H為變量“j”低八位,存儲(chǔ)“CD”。
第二次單擊“Step”按鈕,執(zhí)行語(yǔ)句:p=0xaa;此時(shí)需觀察Memory2窗口內(nèi)數(shù)據(jù),如圖5所示。

由調(diào)試結(jié)果可知,0CH處值由00變?yōu)椤?/font>AAH”,程序相吻合。這里需要注意,在Keil C-51編譯環(huán)境下,指針變量,不管長(zhǎng)度是單字節(jié)或是雙字節(jié),指針變量所占字節(jié)數(shù)為3字節(jié)。故此處“AAH”不是存儲(chǔ)在0AH而存儲(chǔ)在0CH(0A+2)地址中。
綜上所述,指針實(shí)際上是變量,都是映射到RAM中的一段存儲(chǔ)空間,區(qū)別是,指針占用3字節(jié),而其他變量可根據(jù)需要設(shè)定其所占RAM是1字節(jié)(char)、2字節(jié)(int)、4字節(jié)(long)。
2 指針作用
指針的作用是什么呢?先來(lái)看下面的程序:
程序②
#include "REG52.H"
unsigned chartab1[8]={0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08};
unsigned char codetab2[8]={0x10,0x20,0x30,0x40,0x50,0x60,0x70,0x80};
unsigned char N1,N2;
void main()
{
N1=tab1[0];
N2=tab2[0];
}
顯然,程序執(zhí)行的結(jié)果是N1=0x01,N2=0x10。這里都是講數(shù)組內(nèi)的數(shù)據(jù)賦值給變量,但存在區(qū)別,tab1數(shù)組使用的是單片機(jī)RAM空間,而tab2數(shù)組使用的是單片機(jī)程序存儲(chǔ)區(qū)(ROM)空間。盡管使用C語(yǔ)言為變量賦值時(shí)語(yǔ)句相同,但編譯結(jié)果并不相同,此程序編譯后的結(jié)果如圖6所示。
由編譯結(jié)果可知,N1=tab1[0]語(yǔ)句實(shí)際上是直接尋址,而N2=tab2[0]是寄存器變址尋址。不管是何種尋址方式,都是將一個(gè)物理地址內(nèi)的數(shù)據(jù)取出來(lái)使用:tab1數(shù)組中,tab[0]對(duì)應(yīng)的RAM地址是0x0A,tab[1]對(duì)應(yīng)的RAM地址是0x0B……以此類(lèi)推;tab2數(shù)組中,tab[0]對(duì)應(yīng)的ROM地址是0x00A5,tab[1]對(duì)應(yīng)的ROM地址是0x00A6……以此類(lèi)推。不管這些數(shù)組或變量所在的RAM或ROM地址如何,用戶(hù)最終需要的是數(shù)組或變量的數(shù)據(jù),而指針,就是通過(guò)變量或數(shù)組的物理地址訪(fǎng)問(wèn)數(shù)據(jù),也就是說(shuō),通過(guò)指針,同樣可以訪(fǎng)問(wèn)數(shù)組或變量數(shù)據(jù)。現(xiàn)將程序②做出調(diào)整,得到程序③如下:
#include "REG52.H"
unsigned chartab1[8]={0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08};
unsigned char code tab2[8]={0x10,0x20,0x30,0x40,0x50,0x60,0x70,0x80};
unsigned char N1,N2;
unsigned char *p;
void main()
{
unsignedchar i;
p=tab1;
for(i=0;i<8;i++,p++)
N1=*p;
p=tab2;
for(i=0;i<8;i++,p++)
N2=*p;
}
程序執(zhí)行結(jié)果:tab1數(shù)組內(nèi)的8個(gè)數(shù)值依次被賦值給N1;tab2數(shù)組內(nèi)的8個(gè)數(shù)值依次被賦值給N2;
程序③執(zhí)行Debug Session功能后,打Watch Windows窗口,在Watch1窗口下添加需要監(jiān)視的變量,此處為“p”和“N1”,如圖7所示。

Value為當(dāng)前變量數(shù)值,程序?yàn)檫\(yùn)行前,p值為0x00,單擊Step按鍵功能后,執(zhí)行p=tab1;p值變?yōu)?/font>0x0A,如圖8所示。

0x0A是什么值呢?將鼠標(biāo)移至tab1數(shù)組位置,可顯示出數(shù)組所在的物理地址,0x0A就是數(shù)組tab1的首地址,如圖9所示。

p=tab1就是將tab1數(shù)組的首地址賦值給變量p,執(zhí)行p++即地址值加1;*p則是此物理地址內(nèi)的具體數(shù)據(jù),因此for循環(huán)中,N1=*p是依次將tab1數(shù)組中的數(shù)據(jù)賦值給變量N1。由此可見(jiàn),指針是作為一個(gè)變量,指向某一個(gè)地址。
那么指針到底是如何將某個(gè)地址內(nèi)的數(shù)據(jù)“拿”出來(lái)的?下面通過(guò)N1=*p語(yǔ)句做演示說(shuō)明,N1=*p編譯后的匯編代碼如圖10所示。

C:0x00A0至C:0x00A9的匯編代碼即是C程序中的N1=*p。程序先將變量p的值賦值給R3、R2、R1三個(gè)通用寄存器,程序?yàn)椋?/font>
MOV R3,p(0x12)
MOV R2,0x13
MOV R1,0x14
然后調(diào)用了一個(gè)子函數(shù):LCALL C?CLDPTR(C:00E4),而C程序中,未定義或使用任何子函數(shù),那么這個(gè)子函數(shù)是哪里來(lái)的?作用是什么?根據(jù)標(biāo)號(hào)C:00E4可找到該子函數(shù),程序代碼如下:
C:0x00E4 BB0106 CJNE R3,#0x01,C:00ED
C:0x00E7 8982 MOV DPL(0x82),R1
C:0x00E9 8A83 MOV DPH(0x83),R2
C:0x00EB E0 MOVX A,@DPTR
C:0x00EC 22 RET
C:0x00ED 5002 JNC C:00F1
C:0x00EF E7 MOV A,@R1
C:0x00F0 22 RET
C:0x00F1 BBFE02 CJNE R3,#0xFE,C:00F6
C:0x00F4 E3 MOVX A,@R1
C:0x00F5 22 RET
C:0x00F6 8982 MOV DPL(0x82),R1
C:0x00F8 8A83 MOV DPH(0x83),R2
C:0x00FA E4 CLR A
C:0x00FB 93 MOVC A,@A+DPTR
C:0x00FC 22 RET
此程序功能是:先用R3寄存器的值與0x01比較,當(dāng)R3的值大于0x01時(shí),再和0xFE做比較,比較的結(jié)果有如下情況:
(1)R3的值等于0x01時(shí),執(zhí)行如下程序:
C:0x00E7 8982 MOV DPL(0x82),R1
C:0x00E9 8A83 MOV DPH(0x83),R2
C:0x00EB E0 MOVX A,@DPTR
C:0x00EC 22 RET
程序功能:讀取擴(kuò)展RAM內(nèi)的數(shù)據(jù)并賦值給A,尋址范圍0~65535。當(dāng)數(shù)組用xdata定義時(shí),會(huì)跳轉(zhuǎn)到此處。
(2)R3的值小于0x01即等于0x00時(shí),執(zhí)行如下程序:
C:0x00EF E7 MOV A,@R1
C:0x00F0 22 RET
程序功能:讀取單片機(jī)內(nèi)部256字節(jié)RAM內(nèi)的數(shù)據(jù)并賦值給A,尋址范圍0~255。當(dāng)數(shù)組用data或idata定義時(shí),會(huì)跳轉(zhuǎn)到此處。如執(zhí)行N1=*p語(yǔ)句時(shí),即跳轉(zhuǎn)到自處,讀取內(nèi)部RAM地址內(nèi)的數(shù)據(jù)。
(3)R3的值不等于0x00或0x01時(shí),通過(guò)JNC指令跳轉(zhuǎn)到C:0x00F1處,開(kāi)始與0xFE做比較。R3的值等于0xFE時(shí),執(zhí)行如下程序:
C:0x00F4 E3 MOVX A,@R1
C:0x00F5 22 RET
程序功能:讀取單片機(jī)片外RAM內(nèi)的數(shù)據(jù)并賦值給A,尋址范圍0~255。當(dāng)數(shù)組用pdata定義時(shí),會(huì)跳轉(zhuǎn)到此處。通常8051單片機(jī)不使用pdata定義變量或數(shù)組。
(4)R3的值不等于0xFE時(shí),即R3的值等于0xFF時(shí),跳轉(zhuǎn)到C:0x00F6處執(zhí)行如下程序:
C:0x00F6 8982 MOV DPL(0x82),R1
C:0x00F8 8A83 MOV DPH(0x83),R2
C:0x00FA E4 CLR A
C:0x00FB 93 MOVC A,@A+DPTR
C:0x00FC 22 RET
程序功能:讀取單片機(jī)內(nèi)部ROM內(nèi)的數(shù)據(jù)并賦值給A,尋址范圍0~65535。當(dāng)數(shù)組用code定義時(shí),如程序③中,tab2數(shù)組用code定義,執(zhí)行p=tab2后,R3的值被賦值為0xFF,再執(zhí)行N2=*p語(yǔ)句時(shí),即跳轉(zhuǎn)到自處,讀取內(nèi)部ROM地址內(nèi)的數(shù)據(jù)。
由此可見(jiàn),子函數(shù)“C?CLDPTR”的作用是,根據(jù)數(shù)據(jù)所在存儲(chǔ)空間,用不同的尋址方式讀取某地址下的數(shù)據(jù)。R3用于確定尋址方式,R3的值與對(duì)應(yīng)的尋址方式對(duì)應(yīng)關(guān)系為:
1、R3值等于0x00時(shí),片內(nèi)RAM間接尋址;此時(shí)數(shù)據(jù)用dataidata定義。
2、R3值等于0x01時(shí),片外RAM(擴(kuò)展RAM)間接尋址;此時(shí)數(shù)據(jù)用xdata定義。
3、R3值等于0xFE時(shí),片外RAM(擴(kuò)展RAM)低246字節(jié)間接尋址;此時(shí)數(shù)據(jù)用pdata定義
4、R3值等于0xFF時(shí),從存儲(chǔ)存儲(chǔ)器(ROM)進(jìn)行變址尋址;此時(shí)數(shù)據(jù)用code定義。
3、指針結(jié)構(gòu)
R3、R2、R1的值是RAM中0x12、0x13、0x14地址內(nèi)的值,即變量p映射的RAM地址。而而8位單片機(jī)中,不管是何種尋址方式,最大尋址范圍是2字節(jié)長(zhǎng)度(0~65535),為什么指針*p卻占用了3字節(jié)RAM空間呢?下面通過(guò)程序④說(shuō)明。
程序④:
#include "REG52.H"
unsigned char tab1[8];
unsigned char idata tab2[8];
unsigned char xdata tab3[8];
unsigned char pdata tab4[8];
unsigned char codetab5[8]={0x10,0x20,0x30,0x40,0x50,0x60,0x70,0x80};
unsigned char *p;
void main()
{
p=tab1;
p=tab2;
p=tab3;
p=tab4;
p=tab5;
}
在Debug Session模式下可知,程序中數(shù)組與變量所映射的物理地址為及物理存儲(chǔ)區(qū)分別為:
tab1 : 0x08~0x0F 單片機(jī)內(nèi)部RAM
tab2: 0x03~0x1A 單片機(jī)內(nèi)部RAM(idata)
tab3: 0x08~0x0F 單片機(jī)擴(kuò)展RAM(xdata)
tab4: 0x00~0x08 單片機(jī)擴(kuò)展RAM低256字節(jié)(pdata)
tab5: 0x0003D~0x0044 單片機(jī)程序存儲(chǔ)區(qū)(code)
p: 0x10~0x12 單片機(jī)內(nèi)部RAM
注:擴(kuò)展RAM可以在物理上可以分為片內(nèi)或片外,如STC15系列增強(qiáng)型單片機(jī)的擴(kuò)展RAM與單片機(jī)是封裝在一起的,即片內(nèi)擴(kuò)展RAM;傳統(tǒng)8051單片機(jī)沒(méi)有片內(nèi)擴(kuò)展RAM,需連接外部RAM芯片,此為片外擴(kuò)展RAM。
在Memory Windows窗口下,監(jiān)視變量p映射的RAM地址:0x10~0x12的數(shù)值變化,如圖11所示。

通過(guò)“Step”功能按鈕執(zhí)行住函數(shù)中的5調(diào)語(yǔ)句,可觀察到0x10~0x12寄存器的數(shù)據(jù)變化:
執(zhí)行p=tab1后,0x10、0x11、0x12:0x00、0x00、0x08
執(zhí)行p=tab2后,0x10、0x11、0x12:0x00、0x00、0x13
執(zhí)行p=tab3后,0x10、0x11、0x12:0x01、0x00、0x08
執(zhí)行p=tab4后,0x10、0x11、0x12:0xFE、0x00、0x00
執(zhí)行p=tab5后,0x10、0x11、0x12:0xFF、0x00、0x3D
由此可知,0x10的賦值取決于p指向的物理存儲(chǔ)區(qū),0x11、0x12的值是數(shù)據(jù)存儲(chǔ)區(qū)的地址。指針?biāo)成涞氖椎刂罚瑫?huì)根據(jù)指向的物理存儲(chǔ)區(qū)被編譯器賦不同的值:0x00,0x01,0xFE,0xFF。這與程序③得到的結(jié)論一致,程序③中,寄存器R3、R2、R1對(duì)應(yīng)值實(shí)際上就是指針?biāo)成涞?/font>3字寄存器數(shù)值。
結(jié)合程序③編譯分析,當(dāng)需要引用某物理地址內(nèi)數(shù)據(jù)時(shí),會(huì)調(diào)用“C?CLDPTR”函數(shù),函數(shù)功能就是根據(jù)這些賦值確定使用何種尋址方式引用數(shù)據(jù)。而這一過(guò)程包括“C?CLDPTR”函數(shù)都是編譯器自動(dòng)完成的。
在匯編語(yǔ)言中,R1寄存器可以用于間接尋址,如:MOV A,@R1。不能寫(xiě)為MOV A,@12H。因此在程序③中,將變量p對(duì)應(yīng)的3字節(jié)數(shù)據(jù)賦值給R3、R2、R1。
綜上所述,Keil C-51編譯環(huán)境下,指針是一個(gè)占3字節(jié)的特殊變量,編譯器編譯程序時(shí),自動(dòng)生成判斷尋址方式的子函數(shù),并根據(jù)根據(jù)目標(biāo)數(shù)據(jù)所在的物理存儲(chǔ)區(qū)不同,為指針首字節(jié)賦值,根據(jù)賦值的不同,進(jìn)行不同方式的尋址;指針的后2字節(jié),用于存放引用的地址。
調(diào)試訓(xùn)練:
下面的程序編譯器會(huì)怎樣編譯?與程序③有何不同?請(qǐng)根據(jù)程序③和程序④的分析方式分析程序⑤的執(zhí)行結(jié)果。
程序⑤
#include "REG52.H"
unsigned char tab1[8];
unsigned char codetab2[8]={0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff};
unsigned char *p;
void main()
{
unsignedchar i;
p=tab1;
for(i=0;i<8;i++,p++)
*p=i;
p=tab2;
for(i=0;i<8;i++,p++)
*p=i;
}
思考:下列語(yǔ)句中:
p=tab2; for(i=0;i<8;i++,p++) *p=i; 執(zhí)行完for循環(huán)后,tab2數(shù)組內(nèi)的值會(huì)改變嗎?為什么? 4、指針意義 在匯編編程中,由于單片機(jī)數(shù)據(jù)存放的物理存儲(chǔ)區(qū)不同,導(dǎo)致有不同的尋址方式,用戶(hù)進(jìn)行必須根據(jù)這一規(guī)律設(shè)計(jì)程序。而在C語(yǔ)言中,不管目標(biāo)數(shù)據(jù)所在的物理存儲(chǔ)區(qū)如何,指針都可指向該地址,并自動(dòng)編譯尋址方式。 但指針并不是萬(wàn)能的,如程序⑤中: p=tab2; for(i=0;i<8;i++,p++) *p=i; 這些語(yǔ)句編譯時(shí)并不會(huì)報(bào)錯(cuò),但卻不能實(shí)現(xiàn)功能,因?yàn)?/font>tab2數(shù)組是定義在程序存儲(chǔ)器(ROM)的常量數(shù)組,ROM內(nèi)的數(shù)據(jù)更改是不能通過(guò)這種方式實(shí)現(xiàn)的。因此,當(dāng)用戶(hù)不明確單片機(jī)的物理存儲(chǔ)區(qū)特性時(shí),使用指針會(huì)容易出錯(cuò)。先將程序⑤中的主函數(shù)語(yǔ)句做如下修改,得到程序⑥: #include"REG52.H" unsignedchar tab1[8]; unsignedchar code tab2[8]={0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff}; unsignedchar *p; voidmain() { unsigned char i; for(i=0;i<8;i++,p++) tab1[ i]=i; for(i=0;i<8;i++,p++) tab2[ i]=i; } 單獨(dú)看第一個(gè)for循環(huán),可實(shí)現(xiàn)與程序⑤一樣的效果,即tab1數(shù)組內(nèi)被賦值為:0,1,2,3,4,5,6,7。 第二個(gè)for循環(huán)從語(yǔ)句上可認(rèn)為是與程序⑤功能相同,但實(shí)際上,不管是程序⑤還是程序⑥,都不能實(shí)現(xiàn)對(duì)tab2數(shù)組的賦值。但在程序⑥中,編譯器會(huì)提示錯(cuò)誤,如圖12所示。 因此,指針的使用不當(dāng),不僅會(huì)帶來(lái)程序運(yùn)行結(jié)果的不正確,同時(shí)也難以發(fā)現(xiàn)這些錯(cuò)誤。 對(duì)比程序⑤和程序⑥中的兩段程序:
p=tab1; for(i=0;i<8;i++,p++) for(i=0;i<8;i++,p++) tab1[ i]=i; *p=i;
它們執(zhí)行的結(jié)果是一樣的,那么哪種更好呢?對(duì)于初學(xué)者來(lái)說(shuō),顯然是后者,因?yàn)楹笳吒子诶斫獬绦蚝x,而前者必須要理解指針在此處的作用;那么對(duì)于有經(jīng)驗(yàn)的程序員呢?也是后者,因?yàn)槌绦驁?zhí)行效率上,后者也要大于前者,因?yàn)槌绦颌菰诰幾g過(guò)程中,編譯器始終會(huì)生成一個(gè)子函數(shù)用于確定尋址方式,再賦值;程序⑥則是直接確定了尋址方式執(zhí)并行進(jìn)行賦值。盡管執(zhí)行效率的降低在接受范圍內(nèi),但對(duì)于一個(gè)簡(jiǎn)單、明了的功能來(lái)說(shuō),用簡(jiǎn)單的方式實(shí)現(xiàn)要比復(fù)雜方式合理。
設(shè)計(jì)者在程序中使用指針的目的往往是讓程序具有可移植性,但8051單片機(jī)的功能是有限的,它實(shí)現(xiàn)的功能相對(duì)固化,如時(shí)間顯示、數(shù)據(jù)采集等等,這些功能確定后,幾乎不會(huì)做出更改,基于此特點(diǎn),8051單片機(jī)的代碼代碼量都不長(zhǎng)。因此即便是不同構(gòu)架的單片機(jī)程序互相移植,代碼的修改并不復(fù)雜,移植過(guò)程中,也幾乎都是針對(duì)不同構(gòu)架單片機(jī)的I/O工作方式不同、指令周期不同做常規(guī)修改;或是關(guān)鍵字的修改。因此合理的設(shè)計(jì)單片機(jī)程序,盡可能的提高程序的效率、穩(wěn)定性、可閱讀性才是程序設(shè)計(jì)的核心主旨。指針在8051單片機(jī)中固然可以使用,但并不能說(shuō)明指針的使用就一定是高效、準(zhǔn)確、易于他人理解。
|
|