嵌入式C编程技术(四)
内容导读:
[编者按] 为使广大嵌入式系统应用技术人员系统地了解和掌握一些先进应用、开发技术,本刊从创刊号起开辟《学习园地》栏目。上半年集中介绍嵌入式C编程技术(一)~(六),内容包括单片机C语言应用程序设计中的变量定义和变量空间、C语言编程技巧、函数有效使用及混合编程技术。 嵌入式C编程技术(四) 北京理工大学马忠梅 (2) 使用指针访问结构数组 这部分描述如何使用指针访问“结构数组”(其元素是结构的数组)。图3显示如何定义1个结构数组。这个例子定义了1个结构数组array\[NPROC\]。它的元素标识是“entry”,数组元素的个数由NPROC指定,存储器要足够大才可作为结构数组的存储空间。 #define NPROC 10 struct entry{/*entry 结构的定义 */ char state; char name; int args; int regs\[10\]; }; struct entry array\[NPROC\];/*array\[\] 结构数组定义*/ array\[0\]array\[1\]array\[NPROC-2\]array\[NPROC-1\]〖〗state〖〗name[]args[]Reg\[0\][]…[]regs\[9\]state〖〗name[]args[]Reg\[0\][]…[]regs\[9\]\: \: \:state〖〗name[]args[]Reg\[0\][]…[]regs\[9\]state〖〗name[]args[]Reg\[0\][]…[]regs\[9\]图3结构数组的定义 通过乘法可计算访问array\[ \] 结构数组的“init”元素所在的地址,过程如下: · 完成乘法,找到array\[ init\] 的偏移量。 · 取得array\[ \] 起始地址。 · 把array\[init\]的偏移量加到array\[ \] 起始地址上。 · 另加上附加的偏移量,访问下一个所需要的元素。 下面是访问array\[ \] 结构数组的程序: 程序1: 1〖3〗#define uint unsigned int〖1〗2〖3〗#define NUM 10〖1〗3〖3〗〖1〗4〖3〗struct entry{〖1〗5〖3〗char state;〖1〗6〖3〗char name;〖1〗7〖3〗uint args;〖1〗8〖3〗};〖1〗9〖1〗10〖3〗struct entry array\[NUM\];〖1〗11〖3〗uint cnt;〖1〗12〖3〗〖1〗13〖3〗void func1(uint init)〖1〗14〖3〗{〖1〗15〖〗1〖〗bit flag=0;〖1〗16〖〗1〖1〗17〖〗1〖〗if (array\[init\].state==`0' 〖1〗18〖〗1〖〗 array\[init\].name==`0' 〖1〗19〖〗1〖〗 array\[init\].args==0)〖1〗20〖〗1〖〗flag=1;〖1〗21〖〗1〖〗else〖1〗22〖〗1〖〗++cnt;〖1〗23〖〗1〖〗} MODULE INFORMATION:STATIC OVERLAYABLE CODE SIZE= 68\ \ \ \ CONSTANT SIZE=\ \ \ \ \ \ \ \ XDATA SIZE=\ \ \ \ \ \ \ \ PDATA SIZE=\ \ \ \ \ \ \ \ DATA SIZE=42 \ \ \ \ IDATA SIZE=\ \ \ \ \ \ \ \ BIT SIZE=\ \ \ \ 1 END OF MODULE INFORMATION. 程序2: 1〖3〗#define uint unsigned int〖1〗2〖3〗#define NUM 10〖1〗3〖3〗〖1〗4〖3〗struct entry{〖1〗5〖3〗char state;〖1〗6〖3〗char name;〖1〗7〖3〗uint args;〖1〗8〖3〗};〖1〗9〖3〗〖1〗10〖3〗struct entry array\[NUM\];〖1〗11〖3〗uint cnt;〖1〗12〖3〗〖1〗13〖3〗void func2(uint init)〖1〗14〖3〗{〖1〗15〖〗1〖〗bit flag=0;〖1〗16〖〗1〖〗struct entry *base;〖1〗17〖〗1〖〗〖1〗18〖〗1〖〗base=&array\[init\];〖1〗19〖〗1〖〗if (base->state==`0' 〖1〗20〖〗1〖〗 base->name==`0' 〖1〗21〖〗1〖〗 base->args==0)〖1〗22〖〗1〖〗flag=1;〖1〗23〖〗1〖〗else〖1〗24〖〗1〖〗++cnt;〖1〗25〖〗1〖〗} MODULE INFORMATION:STATIC OVERLAYABLE CODE SIZE= 58\ \ \ \ CONSTANT SIZE=\ \ \ \ \ \ \ \ XDATA SIZE=\ \ \ \ \ \ \ \ PDATA SIZE=\ \ \ \ \ \ \ \ DATA SIZE=42 \ \ \ \ IDATA SIZE=\ \ \ \ \ \ \ \ BIT SIZE=\ \ \ \ 1 END OF MODULE INFORMATION. 程序1的func1( )函数访问结构数组元素;“if”条件表达式访问结构数组的“state ”、“name”和“args”成员。每次访问1个成员,func1( )都要进行上述的地址计算。在“if”条件表达式中使用的结构数组“init”元素的下标是1个不变量。然而在条件表达式中,为找“array\[ \]”结构数组的第“init”结构地址的计算进行了3次,每次得到同样的结果。 程序2的func2( )使用1个变量来访问结构数组的元素。“array\[ init\]”结构数组的地址赋给“base”指针,指针指向“entry”结构。地址采用这种方法,上述的地址计算过程只要1。后续的访问结构数组的“state ”、“name”和“args”成员就可基于“base”指针,这可相应减少运行时间。 访问结构数组时,使用指针不仅可以减少运行时间而且可以减少代码长度。从这个意义上讲,使用指针减少了地址计算所需的时间。使用指针访问结构数组元素也可以看作是消除公共表达式的另一种方法。 (3) 指针嵌套和处理效率的降低 指针变量方便、有效。尽管如此,取决于如何使用指针,它们还有可能降低效率。这部分解释了由于使用嵌套指针,使得效率是如何下降的。 在C语言中,指针变量的嵌套可很容易地访问复杂数据结构的元素,如图4所示。当指针嵌套太深,常有结构的访问,处理效率就受到影响。 #define LEN 20 struct entry{ int state; int next; int prev; struct{ int addr; char name\[LEN\]; }*list1; struct{ int maxaddr; int end; }*list2; }; statenextprev*list1*list2〖〗 〖〗addr[]name\[0\][]\:[]name\[LEN\ 1\]maxaddr[]end图4复杂数据结构的定义和访问 下面的程序是访问图4数据结构的例子。 程序1: 1〖3〗#define uchar unsigned char〖1〗2〖3〗#define uint unsigned int〖1〗3〖3〗#define LEN 10〖1〗4〖1〗5〖3〗struct dat{〖1〗6〖3〗uint state;〖1〗7〖3〗uint next;〖1〗8〖3〗uint prev;〖1〗9〖3〗struct{〖1〗10〖3〗uint addr;〖1〗11〖3〗uchar name\[LEN\];〖1〗12〖3〗}*list;〖1〗13〖3〗};〖1〗14〖3〗〖1〗15〖3〗void func1(struct dat *proc1,struct dat *proc2)〖1〗16〖3〗{〖1〗17〖〗1〖〗uchar i;〖1〗18〖〗1〖〗for(i=0;ilist->name\[i\]=proc2->list->name\[i\];〖1〗20〖〗1〖〗}程序2: 1〖3〗#define uchar unsigned char〖1〗2〖3〗#define uint unsigned int〖1〗3〖3〗#define LEN 10〖1〗4〖1〗5〖3〗struct dat{〖1〗6〖3〗uint state;〖1〗7〖3〗uint next;〖1〗8〖3〗uint prev;〖1〗9〖3〗struct{〖1〗10〖3〗uint addr;〖1〗11〖3〗uchar name\[LEN\];〖1〗12〖3〗}*list;〖1〗13〖3〗};〖1〗14〖1〗15〖3〗void func2(struct dat *proc1,struct dat *proc2)〖1〗16〖3〗{〖1〗17〖〗1〖〗uchar i;〖1〗18〖〗1〖〗uchar *p1,*p2;〖1〗19〖〗1〖〗for(p1=proc1->list->name,p2=proc2->〖1〗20〖〗1〖〗list->name,i=0;i0;i--);〖1〗11〖〗1〖1〗12〖〗1〖〗i=10;〖1〗13〖〗1〖〗while(i--);〖1〗14〖〗1〖1〗15〖〗1〖〗for(j=0;j<10;j++);〖1〗16〖〗1〖〗〖1〗17〖〗1〖〗j=10;〖1〗18〖〗1〖〗while(j--);〖1〗19〖〗1〖〗}; FUNCTION main (BEGIN) ;SOURCE LINE # 6 ;SOURCE LINE # 7 ;SOURCE LINE # 8 0000 E4〖3〗CLRA〖1〗0001 F500〖〗R〖〗MOVi,A〖1〗0003〖〗?C0001:〖1〗0003 E500〖〗R〖〗MOVA,i〖1〗0005 C3〖3〗CLRC〖1〗0006 940A〖3〗SUBBA,#0AH〖1〗0008 5004〖3〗JNC?C0002〖1〗000A 0500〖〗R〖〗INCi〖1〗000C 80F5〖3〗SJMP?C0001〖1〗000E〖〗?C0002:〖〗〖3〗;SOURCE LINE # 10〖1〗000E 75000A〖〗R〖〗MOVi,#0AH〖1〗0011〖〗?C0004:〖1〗0011 E500〖〗R〖〗MOVA,i〖1〗0013 D3〖3〗SETBC〖1〗0014 9400〖3〗SUBBA,#00H〖1〗0016 4004〖3〗JC?C0005〖1〗0018 1500〖〗R〖〗DECi〖1〗001A 80F5〖3〗SJMP?C0004〖1〗001C〖〗?C0005:〖〗〖3〗;SOURCE LINE # 12〖1〗001C 75000A〖〗R〖〗MOVi,#0AH〖1〗001F〖〗?C0007:〖〗〖3〗;SOURCE LINE # 13〖1〗001F AF00〖〗R〖〗MOVR7,i〖1〗0021 1500〖〗R〖〗DECi〖1〗0023 EF〖3〗MOVA,R7〖1〗0024 70F9〖3〗JNZ?C0007〖1〗0026〖〗?C0008:〖〗〖3〗;SOURCE LINE # 15〖1〗0026 E4〖3〗CLRA〖1〗0027 F500〖〗R〖〗MOVj,A〖1〗0029 F500〖〗R〖〗MOVj+01H,A〖1〗002B〖〗?C0009:〖1〗002B C3〖3〗CLRC〖1〗002C E500〖〗R〖〗MOV A,j+01H〖1〗002E 940A〖3〗SUBBA,#0AH〖1〗0030 E500〖〗R〖〗MOV A,j〖1〗0032 9400〖3〗SUBBA,#00H〖1〗0034 500A〖3〗JNC?C0010〖1〗0036 0500〖〗R〖〗INCj+01H〖1〗0038 E500〖〗R〖〗MOVA,j+01H〖1〗003A 7002〖3〗JNZ?C0015〖1〗003C 0500〖〗R〖〗INCj〖1〗003E〖〗?C0015:〖1〗003E 80EB〖3〗SJMP?C0009〖1〗0040〖〗?C0010:〖〗〖3〗;SOURCE LINE # 17〖1〗0040 750000〖〗R〖〗MOVj,#00H〖1〗0043 75000A〖〗R〖〗MOVj+01H,#0AH〖1〗0046〖〗?C0012:〖〗〖3〗;SOURCE LINE # 18〖1〗0046 E500〖〗R〖〗MOVA,j+01H〖1〗0048 1500〖〗R〖〗DECj+01H〖1〗004A AE00〖〗R〖〗MOVR6,j〖1〗004C 7002〖3〗JNZ?C0016〖1〗004E 1500〖〗R〖〗DECj〖1〗0050〖〗?C0016:〖1〗0050 4E〖3〗ORLA,R6〖1〗0051 70F3〖3〗JNZ?C0012〖〗〖3〗;SOURCE LINE # 19〖1〗0053〖〗?C0014:〖1〗0053 22〖3〗RET〖3〗; FUNCTION main (END)从上面程序可以得出结论:应尽可能使用字符型变量,while(i--)循环的代码效率要高一些。 4 函数返回地址的控制 在编写C程序过程中,有时希望从函数内跳出来,转到某处执行程序。特别是在中断处理程序中出错等的情况下,希望提前返回,并且转到特定的地址。在用汇编语言编制程序时,都知道中断返回要采用RETI指令。中断服务程序的最后1条指令必须是RETI,否则从硬件上就不能中断,再进入其他的中断服务程序。下面是RETI指令完成的操作: RETI;PC15~8←(SP),SP←SP-1 ;PC7~0←(SP),SP←SP-1 实际上,在C语言中使用堆栈并不神秘,控制返回地址也不复杂。请看下面程序: 1〖3〗#include 〖1〗2〖3〗#define uchar unsigned char〖1〗3〖3〗#define uint unsigned int〖1〗4〖3〗#define SLAVE 2〖1〗5〖1〗6〖3〗void SIOINT(void) interrupt 4 using 1〖1〗7〖3〗{〖1〗8〖〗1〖〗uchar a,i;〖1〗9〖〗1〖〗uchar xdata *data Rbuf;〖1〗10〖〗1〖〗uchar data * data sppt;〖1〗11〖〗1〖1〗12〖〗1〖〗RI=0;〖1〗13〖〗1〖〗ES=0;〖1〗14〖〗1〖〗a=SBUF;〖1〗15〖〗1〖〗if(a!=SLAVE)〖1〗16〖〗1〖〗{ SBUF=0xff;〖1〗17〖〗2〖〗while(TI!=1);TI=0;〖1〗18〖〗2〖〗ES=1;goto retend;〖1〗19〖〗2〖〗}〖1〗20〖〗1〖〗SBUF=a;〖1〗21〖〗1〖〗while(TI!=1);TI=0;〖1〗22〖〗1〖〗while(RI!=1);RI=0;〖1〗23〖〗1〖〗a=SBUF;〖1〗24〖〗1〖〗SM2=0;〖1〗25〖〗1〖〗Rbuf=0;〖1〗26〖〗1〖〗*Rbuf=a;〖1〗27〖〗1〖〗SBUF=0xaa;〖1〗28〖〗1〖〗while(TI!=1);TI=0;〖1〗29〖〗1〖〗for(i=1;i<16;i++)〖1〗30〖〗1〖〗{ while(RI!=1);RI=0;〖1〗31〖〗2〖〗a=SBUF;〖1〗32〖〗2〖〗Rbuf\[i\]=a;〖1〗33〖〗2〖〗SBUF=0xaa;〖1〗34〖〗2〖〗while(TI!=1);TI=0;〖1〗35〖〗2〖〗}〖1〗36〖〗1〖1〗37〖〗1〖〗reti: ES=1;SM2=1;〖1〗38〖〗1〖〗sppt=SP;〖1〗39〖〗1〖〗sppt-=4;〖1〗40〖〗1〖〗*sppt=0x0;sppt--;*sppt=0x0;〖1〗41〖〗1〖〗retend:;〖1〗42〖〗1〖〗} ; FUNCTION SIOINT (BEGIN) 0000 C0E0〖3〗PUSHACC〖1〗0002 C083〖3〗PUSHDPH〖1〗0004 C082〖3〗PUSHDPL〖1〗0006 C0D0〖3〗PUSHPSW〖1〗0008 75D008〖3〗MOVPSW,#08H〖〗[3];SOURCE LINE # 6[3];SOURCE LINE # 12〖1〗000B C298〖3〗CLRRI[3];SOURCE LINE # 13〖1〗000D C2AC〖3〗CLRES[3];SOURCE LINE # 14;\ \ \ \ Variable `a' assigned to Register `R7' \ \ \ \ 000F AF99〖3〗MOVR7,SBUF〖〗[3];SOURCE LINE # 15〖1〗0011 EF〖3〗MOVA,R7〖1〗0012 6402〖3〗XRLA,#02H〖1〗0014 600C〖3〗JZ?C0001〖〗[3];SOURCE LINE # 16〖1〗0016 7599FF〖3〗MOVSBUF,#0FFH〖1〗0019〖〗?C0002:〖〗〖〗[3];SOURCE LINE # 17〖1〗0019 3099FD〖3〗JNBTI,?C0002〖1〗001C〖〗?C0003:〖1〗001C C299〖3〗CLRTI〖〗[3];SOURCE LINE # 18〖1〗001E D2AC〖3〗SETBES〖1〗0020 8056〖3〗SJMP?C0019〖〗[3];SOURCE LINE # 19〖1〗0022〖〗?C0001:〖〗[3];SOURCE LINE # 20〖1〗0022 8F99〖3〗MOVSBUF,R7〖1〗0024〖〗?C0005:〖〗[3];SOURCE LINE # 21〖1〗0024 3099FD〖3〗JNBTI,?C0005〖1〗0027〖〗?C0006:〖1〗0027 C299〖3〗CLRTI〖1〗0029〖〗?C0007:〖〗[3];SOURCE LINE # 22〖1〗0029 3098FD〖3〗JNBRI,?C0007〖1〗002C〖〗?C0008:〖1〗002C C298〖3〗CLRRI〖〗[3];SOURCE LINE # 23〖1〗002E AF99〖3〗MOVR7,SBUF〖〗[3];SOURCE LINE # 24〖1〗0030 C29D〖3〗CLRSM2〖〗[3];SOURCE LINE # 25;\ \ \ \ Variable `Rbuf' assigned to Register `R4/R5' \ \ \ \ 0032 E4〖3〗CLRA〖1〗0033 FD〖3〗MOVR5,A〖1〗0034 FC〖3〗MOVR4,A〖〗[3];SOURCE LINE # 26〖1〗0035 F582〖3〗MOVDPL,A〖1〗0037 F583〖3〗MOVDPH,A〖1〗0039 EF〖3〗MOVA,R7〖1〗003A F0〖3〗MOVX@DPTR,A[3];SOURCE LINE # 27〖1〗003B 7599AA〖3〗MOVSBUF,#0AAH〖1〗003E〖〗?C0009:〖〗[3];SOURCE LINE # 28〖1〗003E 3099FD〖3〗JNBTI,?C0009〖1〗0041〖〗?C0010:〖1〗0041 C299〖3〗CLRTI〖〗[3];SOURCE LINE # 29;\ \ \ \ Variable `i ' assigned to Register `R6' \ \ \ \ 0043 7E01〖3〗MOVR6,#01H〖1〗0045〖〗?C0011:〖1〗0045 EE〖3〗MOVA,R6〖1〗0046 C3〖3〗CLRC〖1〗0047 9410〖3〗SUBBA,#010H〖1〗0049 501C〖3〗JNCreti〖〗[3];SOURCE LINE # 30〖1〗004B〖〗?C0014:〖1〗004B 3098FD〖3〗JNBRI,?C0014〖1〗004E〖〗?C0015:〖1〗004E C298〖3〗CLRRI〖〗[3];SOURCE LINE # 31〖1〗0050 AF99〖3〗MOVR7,SBUF〖〗[3];SOURCE LINE # 32〖1〗0052 ED〖3〗MOVA,R5〖1〗0053 2E〖3〗ADDA,R6〖1〗0054 F582〖3〗MOVDPL,A〖1〗0056 E4〖3〗CLRA〖1〗0057 3C〖3〗ADDCA,R4〖1〗0058 F583〖3〗MOVDPH,A〖1〗005A EF〖3〗MOVA,R7〖1〗005B F0〖3〗MOVX@DPTR,A〖〗[3];SOURCE LINE # 33〖1〗005C 7599AA〖3〗MOV SBUF,#0AAH〖1〗005F〖〗?C0016:〖〗[3];SOURCE LINE # 34〖1〗005F 3099FD〖3〗JNBTI,?C0016〖1〗0062〖〗?C0017:〖1〗0062 C299〖3〗CLRTI〖〗[3];SOURCE LINE # 35〖1〗0064 0E〖3〗INCR6〖1〗0065 80DE〖3〗SJMP?C0011〖〗[3];SOURCE LINE # 37〖1〗0067〖〗reti:〖1〗0067 D2AC〖3〗SETBES〖1〗0069 D29D〖3〗SETBSM2〖〗[3];SOURCE LINE # 38〖1〗006B AF81〖3〗MOV R7,SP;\ \ \ \ Variable `sppt' assigned to Register `R7' \ \ \ \ ;SOURCE LINE # 39 006D 74FC〖3〗MOVA,#0FCH〖1〗006F 2F〖3〗ADDA,R7〖1〗0070 FF〖3〗MOVR7,A〖〗[3];SOURCE LINE # 40〖1〗0071 F8〖3〗MOVR0,A〖1〗0072 E4〖3〗CLRA〖1〗0073 F6〖3〗MOV@R0,A〖1〗0074 1F〖3〗DECR7〖1〗0075 A80F〖3〗MOVR0,AR7〖1〗0077 F6〖3〗MOV@R0,A〖〗[3];SOURCE LINE # 41〖1〗0078〖〗retend:〖〗[3];SOURCE LINE # 42〖1〗0078〖〗?C0019:〖1〗0078 D0D0〖3〗POPPSW〖1〗007A D082〖3〗POPDPL〖1〗007C D083〖3〗POPDPH〖1〗007E D0E0〖3〗POPACC〖1〗0080 32〖3〗RETI; FUNCTION SIOINT (END) 上面的程序是1个多机通信的中断服务程序,从机的地址用“SLAVE”定义。从机地址和功能字节第9位为1,后跟15个字节的数据,功能字节和数据接收到后放入到“Rbuf\[ \]”数组中。服务程序的正常返回处是“retend:”,若出错就可以跳到“reti:”处,然后返回到0000H开始重新执行程序。值得注意的是:处理堆栈时,使用了“sppt\ =4;”,这是由于在中断服务程序入口处用了4条“PUSH”指令向堆栈中压入4个字节内容保护现场。 “*sppt=0x0;sppt\ \ ;*sppt=0x0;”语句是修改堆栈中的内容以备中断返回的“RETI”指令使用。在中断服务程序中是不可以轻易跳出的,因而这是1个非常重要的嵌入式C编程技巧。 (待续)
标签:
来源:单片机与嵌入式系统应用 作者:北京理工大学 马忠梅 时间:2006/2/12 0:00:00