嵌入式C编程技术(五)
导读:
关键字:
[编者按] 为使广大嵌入式系统应用技术人员系统地了解和掌握一些先进应用、开发技术,本刊从创刊号起开辟《学习园地》栏目。上半年集中介绍嵌入式C编程技术(一)~(六),内容包括单片机C语言应用程序设计中的变量定义和变量空间、C语言编程技巧、函数有效使用及混合编程技术。 嵌入式C编程技术(五) 北京理工大学马忠梅 三、 函数的有效使用 这部分包括使用宏函数减少函数的调用、参数传递的方法和函数返回值的类型。 1 使用宏函数减少函数的调用 (1) 什么是宏 使用“#define”预处理命令定义宏: #define 宏名 文本串 “#define”是用一串字符替换另一串字符的命令。一旦定义了“#define 串”,每次宏名在程序中出现时都会用指定的串来代替。 例如: #define ERR 0 宏定义后,串“ERR”每次在程序中出现时都用0来代替。 宏也可以有参数,如 #define 宏名(参数表)文本串 即定义了1个宏函数。在这种情况下,一旦用“#define 串”定义了1个宏函数,后续的程序中出现的宏函数都会由指定的串来代替。 例如,下面的宏函数定义: #define max(x,y) ((x>y)?x:y) 每次“max(x,y)”出现在程序中,都要完成对x,y值的((x>y)?x:y)操作。 宏定义和宏函数程序举例如下: 程序1 #define SIZE 100 #define check(x) ((`0'<=x && x<=`9') \(`a'<=x && x<=`f ') \(`A'<=x && x<=`F ')) char buf\[SIZE\]; int macro_1( ) { int i,cnt=0; for(i=0;i<100;++i) if(check(buf\[i\])) ++cnt; return(cnt); } 程序2 char buf\[100\]; int macro_1( ) { int i,cnt=0; for(i=0;i<100;++i) if( (`0'<=buf\[i\] && buf\[i\]<=`9') \(`a'<=buf\[i\] && buf\[i\]<=`f') \(`A'<=buf\[i\] && buf\[i\]<=`F') ) ++cnt; return(cnt); } 程序2是程序1在宏和宏函数使用后的实际替换情况。在这个例子里,一个宏定义定义了串“SIZE”为100,另一个定义了宏函数“check(x)”。当串“SIZE”出现在“macro_1( )”函数的执行过程中时,它用“100”来替换;而且,当串“check(x)”出现时,它用宏函数的函数内容替换,然后完成处理过程。 (2) 函数和宏函数的比较 这部分讨论常规函数和宏函数的区别。 当编写大的软件系统时,经常使用的只有几步的处理过程常常作为函数。但是,尽管是短函数,也总须要使用堆栈空间,至少是保留返回地址。 建议像这种情况使用宏函数。宏函数同主程序分开,用预处理命令定义,然后通过编译程序把它们嵌入到使用宏的函数中。这样,宏出现得越多,生成的代码就越大。 下面是改成宏函数后省去的开销: · 参数传递处理; · 返回地址保存; · 局部变量空间; · 返回值处理。 通过使用宏函数,就可以减少上述和函数调用有关的开销,从而减少了相应的运行时间。 下面是使用函数和使用宏函数的比较。 程序1 1〖3〗#define SIZE 100〖1〗2〖1〗3〖3〗int check(char i);〖1〗4〖3〗char buf\[SIZE\];〖1〗5〖1〗6〖3〗int macro_1()〖1〗7〖3〗{〖1〗8〖〗1〖〗int i,cnt=0;〖1〗9〖〗1〖1〗10〖〗1〖〗for(i=0;i<100;++i)〖1〗11〖〗1〖〗if(check(buf\[i\]))〖1〗12〖〗1〖〗++cnt;〖1〗13〖〗1〖1〗14〖〗1〖〗return(cnt);〖1〗15〖〗1〖〗}〖1〗16〖1〗17〖3〗int check(char i)〖1〗18〖3〗{ 〖1〗19〖〗1〖〗if((`0'<=i && i<=`9') \\〖1〗20〖〗1〖〗(`a'<=i && i<=`f') \\〖1〗21〖〗1〖〗(`A'<=i && i<=`F'))〖1〗22〖〗1〖〗return(1);〖1〗23〖〗1〖〗else〖1〗24〖〗1〖〗return(0);〖1〗25〖〗1〖〗} MODULE INFORMATION:STATIC OVERLAYABLE CODE SIZE= 129\ \ \ \ CONSTANT SIZE=\ \ \ \ \ \ \ \ XDATA SIZE=\ \ \ \ \ \ \ \ PDATA SIZE=\ \ \ \ \ \ \ \ DATA SIZE=1004 IDATA SIZE=\ \ \ \ \ \ \ \ BIT SIZE=\ \ \ \ \ \ \ \ END OF MODULE INFORMATION. 程序2 1〖3〗#define SIZE 100〖1〗2〖1〗3〖3〗#define check(x) ((`0'<=x && x<=`9') \\〖1〗4〖3〗(`a'<=x && x<=`f') \\〖1〗5〖3〗(`A'<=x && x<=`F'))〖1〗6〖1〗7〖3〗char buf\[SIZE\];〖1〗8〖3〗〖1〗9〖3〗int macro_1()〖1〗10〖3〗{〖1〗11〖〗1〖〗int i,cnt=0;〖1〗12〖〗1〖1〗13〖〗1〖〗for(i=0;i<100;++i)〖1〗14〖〗1〖〗if(check(buf\[i\]))〖1〗15〖〗1〖〗++cnt;〖1〗16〖〗1〖1〗17〖〗1〖〗return(cnt);〖1〗18〖〗1〖〗} MODULE INFORMATION:STATIC OVERLAYABLE CODE SIZE= 94\ \ \ \ CONSTANT SIZE=\ \ \ \ \ \ \ \ XDATA SIZE=\ \ \ \ \ \ \ \ PDATA SIZE=\ \ \ \ \ \ \ \ DATA SIZE=100\ \ \ \ IDATA SIZE=\ \ \ \ \ \ \ \ BIT SIZE=\ \ \ \ \ \ \ \ END OF MODULE INFORMATION. 程序1处理过程定义成函数。“check( )”函数调用完成对“char”型数组“buf\[ \]”的处理。程序2处理过程定义成宏函数。程序执行时,由于“check( )”宏函数处理扩展成行内代码,根本不需要使用堆栈保存返回地址,也不需要参数传递。尽管由于宏函数代码嵌入到宏出现的程序主体中,多次使用宏函数会比使用常规的函数调用产生更多的代码,但减少了堆栈的使用和内部RAM单元的使用。 在这个例子里,由于数组元素作为函数的参数,每次使用函数都要进行地址的计算。这就是为什么宏函数比调用函数产生代码更少并且运行时间更短。当在循环中使用短函数时,宏函数通常比常规函数调用更有效。 值得注意的是:在函数调用时完成数据类型的检查,即被调用函数的参数取决于函数初始定义时的数据类型说明。对于宏函数不再说明数据类型,因而支持任何数据类型。“check(100)”或“check(2.5)”,不会产生编译错误,但可能得到意想不到的结果。在使用宏函数时要格外小心。 2 函数参数传递的方法 下面用例子来分析参数传递的方法。在这些例子中,变量定义成结构。参数传递使用的方法有: · 常规参数传递; · 参数结构传递; · 参数结构地址传递。 参数数据的结构如图5所示。 struct int data1; int data2; char *msg; } 结构类型定义〖〗data1data2*msg字串〖〗“Hello!!” 图5参数定义成结构 (1) 常规参数传递 下面是常规参数传递的例子。参数保留在寄存器中,参数越多,使用的寄存器就越多。例子调用“func_sub1( )”函数传递3个参数数据,第17行和第19行产生的代码把参数拷贝到寄存器R1,R2,R3,R4,R5,R6和R7中。 1〖3〗#define FIRST 20〖1〗2〖3〗#define SECOND 40〖1〗3〖1〗4〖3〗struct list{〖1〗5〖3〗int dat1;〖1〗6〖3〗int dat2;〖1〗7〖3〗char *msg;〖1〗8〖3〗};〖1〗9〖3〗int sub1(int a,int b,char *p);〖1〗10〖1〗11〖3〗main()〖1〗12〖3〗{〖1〗13〖〗1〖〗struct list buff;〖1〗14〖〗1〖1〗15〖〗1〖〗buff.dat1=FIRST;〖1〗16〖〗1〖〗buff.dat2=SECOND;〖1〗17〖〗1〖〗buff.msg="Hello!!";〖1〗18〖〗1〖1〗19〖〗1〖〗sub1(buff.dat1,buff.dat2,buff.msg);〖1〗20〖〗1〖〗}〖1〗21〖1〗22〖3〗int sub1(int a,int b,char *p)〖1〗23〖3〗{〖1〗24〖〗1〖〗int total;〖1〗25〖〗1〖〗char *c;〖1〗26〖〗1〖〗char array\[10\];〖1〗27〖〗1〖1〗28〖〗1〖〗total=a+b;〖1〗29〖〗1〖〗c=array;〖1〗30〖〗1〖〗while(*p){〖1〗31〖〗2〖〗*c++=*p++;〖1〗32〖〗2〖〗}〖1〗33〖〗1〖〗return(total);〖1〗34〖〗1〖〗}; SOURCE LINE # 17 000C 7B05〖3〗MOV〖〗R3,#05H〖1〗000E 7A00〖〗R〖〗MOV〖〗R2,#HIGH ?SC_0〖1〗0010 7900〖〗R〖〗MOV〖〗R1,#LOW ?SC_0〖1〗0012 8B00〖〗R〖〗MOV〖〗buff+04H,R3〖1〗0014 8A00〖〗R〖〗MOV〖〗buff+05H,R2〖1〗0016 8900〖〗R〖〗MOV〖〗buff+06H,R1〖4〗; SOURCE LINE # 19〖1〗0018 AF00〖〗R〖〗MOV〖〗R7,buff+01H〖1〗001A AE00〖〗R〖〗MOV〖〗R6,buff〖1〗001C AD00〖〗R〖〗MOV〖〗R5,buff+03H〖1〗001E AC00〖〗R〖〗MOV〖〗R4,buff+02H〖1〗0020 120000〖〗R〖〗LCALL〖〗_sub1 MODULE INFORMATION:STATIC OVERLAYABLE CODE SIZE= 109\ \ \ \ CONSTANT SIZE=8\ \ \ \ XDATA SIZE=\ \ \ \ \ \ \ \ PDATA SIZE=\ \ \ \ \ \ \ \ DATA SIZE=\ \ \ \ 25 IDATA SIZE=\ \ \ \ \ \ \ \ BIT SIZE=\ \ \ \ \ \ \ \ END OF MODULE INFORMATION. (2) 参数结构传递 下面是参数结构传递的例子。完成参数结构传递的C程序非常简单。在调用“func_sub2( )”函数之前,第19行产生的代码不仅把参数拷贝到寄存器中,而且还进行了数据的拷贝。 1〖3〗#define FIRST 20〖1〗2〖3〗#define SECOND 40〖1〗3〖1〗4〖3〗struct list{〖1〗5〖3〗int dat1;〖1〗6〖3〗int dat2;〖1〗7〖3〗char *msg;〖1〗8〖3〗};〖1〗9〖3〗int sub2(struct list str_data);〖1〗10〖1〗11〖3〗main()〖1〗12〖3〗{〖1〗13〖〗1〖〗struct list buff;〖1〗14〖〗1〖1〗15〖〗1〖〗buff.dat1=FIRST;〖1〗16〖〗1〖〗buff.dat2=SECOND;〖1〗17〖〗1〖〗buff.msg="Hello!!";〖1〗18〖〗1〖1〗19〖〗1〖〗sub2(buff);〖1〗20〖〗1〖〗}〖1〗21〖1〗22〖3〗int sub2(struct list str_data)〖1〗23〖3〗{〖1〗24〖〗1〖〗int total;〖1〗25〖〗1〖〗char *c;〖1〗26〖〗1〖〗char array\[10\];〖1〗27〖〗1〖1〗28〖〗1〖〗total=str_data.dat1+str_data.dat2;〖1〗29〖〗1〖〗c=array;〖1〗30〖〗1〖〗while(*str_data.msg){〖1〗31〖〗2〖〗*c++=*str_data.msg++;〖1〗32〖〗2〖〗}〖1〗33〖〗1〖〗return(total);〖1〗34〖〗1〖〗}〖3〗; SOURCE LINE # 19 0015 7800〖〗R〖〗MOV〖〗R0,#LOW ?sub2?BYTE〖1〗0017 7C00〖〗R〖〗MOV〖〗R4,#HIGH ?sub2?BYTE〖1〗0019 7D04〖3〗MOV〖〗R5,#04H〖1〗001B 7B04〖3〗MOV〖〗R3,#04H〖1〗001D 7A00〖〗R〖〗MOV〖〗R2,#HIGH buff〖1〗001F 7900〖〗R〖〗MOV〖〗R1,#LOW buff〖1〗0021 7E00〖3〗MOV〖〗R6,#00H〖1〗0023 7F07〖3〗MOV〖〗R7,#07H〖1〗0025 120000〖〗E〖〗LCALL〖〗?C_COPY〖1〗0028 120000〖〗R〖〗LCALL〖〗sub2 MODULE INFORMATION:STATIC OVERLAYABLE CODE SIZE= 109\ \ \ \ CONSTANT SIZE=8\ \ \ \ XDATA SIZE=\ \ \ \ \ \ \ \ PDATA SIZE=\ \ \ \ \ \ \ \ DATA SIZE=\ \ \ \ 27 IDATA SIZE=\ \ \ \ \ \ \ \ BIT SIZE=\ \ \ \ \ \ \ \ END OF MODULE INFORMATION. (3) 参数结构地址传递 下面是参数结构地址传递的例子。用这种方法,仅把结构的地址拷贝到寄存器中,然后再调用函数“func_sub3( )”。第19行产生的代码只把参数拷贝到R1,R2和R3中。它比方法1或方法2所需要传递的参数的字节数更少。 1〖3〗#define FIRST 20〖1〗2〖3〗#define SECOND 40〖1〗3〖1〗4〖3〗struct list{〖1〗5〖3〗int dat1;〖1〗6〖3〗int dat2;〖1〗7〖3〗char *msg;〖1〗8〖3〗};〖1〗9〖3〗int sub3(struct list *poi_data);〖1〗10〖1〗11〖3〗main()〖1〗12〖3〗{〖1〗13〖〗1〖〗struct list buff;〖1〗14〖〗1〖1〗15〖〗1〖〗buff.dat1=FIRST;〖1〗16〖〗1〖〗buff.dat2=SECOND;〖1〗17〖〗1〖〗buff.msg="Hello!!";〖1〗18〖〗1〖1〗19〖〗1〖3〗sub3(&buff);〖1〗20〖〗1〖3〗}〖1〗21〖1〗22〖3〗int sub3(struct list *poi_data)〖1〗23〖3〗{〖1〗24〖〗1〖〗int total;〖1〗25〖〗1〖〗char *c,*d;〖1〗26〖〗1〖〗char array\[10\];〖1〗27〖〗1〖1〗28〖〗1〖〗total=poi_data->dat1+poi_data->dat2;〖1〗29〖〗1〖〗c=array;〖1〗30〖〗1〖〗d=poi_data->msg;〖1〗31〖〗1〖〗while(*d){〖1〗32〖〗2〖〗*c++=*d++;〖1〗33〖〗2〖〗}〖1〗34〖〗1〖〗return(total);〖1〗35〖〗1〖〗}; SOURCE LINE # 19 0015 7B04〖3〗MOV〖〗R3,#04H〖1〗0017 7A00〖〗R〖〗MOV〖〗R2,#HIGH buff〖1〗0019 7900〖〗R〖〗MOV〖〗R1,#LOW buff〖1〗001B 120000〖〗R〖〗LCALL〖〗_sub3 MODULE INFORMATION:STATIC OVERLAYABLE CODE SIZE= 122\ \ \ \ CONSTANT SIZE=8\ \ \ \ XDATA SIZE=\ \ \ \ \ \ \ \ PDATA SIZE=\ \ \ \ \ \ \ \ DATA SIZE=\ \ \ \ 26 IDATA SIZE=\ \ \ \ \ \ \ \ BIT SIZE=\ \ \ \ \ \ \ \ END OF MODULE INFORMATION. 这种方法实际上是把结构的指针传递给函数,处理过程直接针对原始结构。如果函数改变了结构成员的值,调用之前的原始结构值就丢失了;而常规的参数传递方法,原始结构值不受影响。上面结构地址传递的例子,为了防止“buff”结构中的“msg”成员值的丢失,在使用“msg”之前,在“func_sub3( )”函数中增加局部变量“d”,把“msg”的值暂时赋给“d”变量。 3 函数返回值的类型 这部分是返回“long”和“float”类型值及返回结构类型值的函数的例子,解释函数返回值的类型。 (1) “long”和“float”型返回值 首先考虑返回4字节“long”或“float”值的函数。下面是返回“long”值函数的例子。 1〖3〗#define MAX 150〖1〗2〖3〗#define OK 1〖1〗3〖3〗#define ERR -1〖1〗4〖1〗5〖3〗long func_long(long dat,int num)〖1〗6〖3〗{〖1〗7〖〗1〖〗long total=0;〖1〗8〖〗1〖〗int i;〖1〗9〖〗1〖1〗10〖〗1〖〗for(i=num;i>0;i--)〖1〗11〖〗1〖〗total+=dat*i;〖1〗12〖〗1〖〗return(total);〖1〗13〖〗1〖〗}〖1〗14〖1〗15〖3〗int func_main(long i_data)〖1〗16〖3〗{〖1〗17〖〗1〖〗long o_data;〖1〗18〖〗1〖〗int cnt=5;〖1〗19〖〗1〖1〗20〖〗1〖〗o_data=func_long(i_data,cnt)+i_data;〖1〗21〖〗1〖〗if(MAX>o_data)〖1〗22〖〗1〖〗return(OK);〖1〗23〖〗1〖〗else〖1〗24〖〗1〖〗return(ERR);〖1〗25〖〗1〖〗} ; SOURCE LINE # 12005F AF00〖〗R〖〗MOV〖〗R7,total+03H〖1〗0061 AE00〖〗R〖〗MOV〖〗R6,total+02H〖1〗0063 AD00〖〗R〖〗MOV〖〗R5,total+01H〖1〗0065 AC00〖〗R〖〗MOV〖〗R4,total; SOURCE LINE # 130067〖〗?C0004:〖1〗0067 22〖〗RET MODULE INFORMATION:STATIC OVERLAYABLE CODE SIZE= 163\ \ \ \ CONSTANT SIZE=\ \ \ \ \ \ \ \ XDATA SIZE=\ \ \ \ \ \ \ \ PDATA SIZE=\ \ \ \ \ \ \ \ DATA SIZE=\ \ \ \ 16 IDATA SIZE=\ \ \ \ \ \ \ \ BIT SIZE=\ \ \ \ \ \ \ \ END OF MODULE INFORMATION. 在程序的第20行,“func_main( )”函数调用返回4字节“long”值的“func_long( )”函数。函数返回值是在函数返回前的第12行产生代码中拷贝到R4~R7的。在这个例子中,“func_long( )”的返回值和“i_data”变量的运算结果赋给了“o_data”变量。像这种情况,也可以定义传递返回值地址的函数,把返回值返回到那个地址中。下面的例子把返回值的长整型变量的地址作为参数传递。 1〖3〗#define MAX 150〖〗2〖3〗#define OK 1〖〗3〖3〗#define ERR -1〖〗4〖1〗5〖3〗void func_p_long(long dat,int num,long *ans)〖〗6〖3〗{〖〗7〖〗1〖〗long total=0;〖〗8〖〗1〖〗int i;〖〗9〖〗1〖1〗10〖〗1〖〗for(i=num;i>0;i--)〖1〗11〖〗1〖〗total+=dat*i;〖1〗12〖〗1〖〗*ans=total;〖1〗13〖〗1〖〗}〖1〗14〖1〗15〖3〗int func_main(long i_data)〖1〗16〖3〗{〖1〗17〖〗1〖〗long o_data;〖1〗18〖〗1〖〗int cnt=5;〖1〗19〖〗1〖1〗20〖〗1〖〗func_p_long(i_data,cnt,&o_data);〖1〗21〖〗1〖〗o_data+=i_data;〖1〗22〖〗1〖〗if(MAX>o_data)〖1〗23〖〗1〖〗return(OK);〖1〗24〖〗1〖〗else〖1〗25〖〗1〖〗return(ERR);〖1〗26〖〗1〖〗} MODULE INFORMATION:STATIC OVERLAYABLE CODE SIZE= 197\ \ \ \ CONSTANT SIZE=\ \ \ \ \ \ \ \ XDATA SIZE=\ \ \ \ \ \ \ \ PDATA SIZE=\ \ \ \ \ \ \ \ DATA SIZE=\ \ \ \ 23 IDATA SIZE=\ \ \ \ \ \ \ \ BIT SIZE=\ \ \ \ \ \ \ \ END OF MODULE INFORMATION. “func_p_long( )”处理的结果直接返回到“func_main( )”函数的“o_data”局部变量。返回4字节“float”型值的参数也可以使用同样的处理:定义1个传递返回值地址的函数,然后由函数把返回值存在这个地址中。但从产生代码的效率来看,这种方法并不可取。(待续)
来源:单片机与嵌入式系统应用 作者:北京理工大学 马忠梅 2006/2/12 0:00:00