前几天在写C51程序时用到了递归,简单程序如下:
void WRITE_ADD(uchar addr,uchar wbyte) { START(); //先发送起始信号 WRITE_BYTE(0xa0); //设备地址+W命令 if(!ERROR_Flag) //正确收到应答 { WRITE_BYTE(addr); //写入地址 } else { ERROR_Flag = 0; //清错误标志 WRITE_ADD(addr,wbyte); //重新写入 } if(!ERROR_Flag) //地址收到正确应答 { WRITE_BYTE(wbyte); //发送要写入的数据 } else { ERROR_Flag = 0; //清错误标志 WRITE_ADD(addr,wbyte); //重新写入 } if(!ERROR_Flag) //正确收到应答 { STOP(); //停止 } else { ERROR_Flag = 0; //清错误标志 WRITE_ADD(addr,wbyte); //重新写入 } }
编译时出现如下警告:
warning C265: '_WRITE_ADD': recursive call to non-reentrant function(循环调用了非可重入函数)。
经过查找资料之后,解决方法是在函数后加入关键字,使函数变成可重入函数:
返回值 函数名(形参) reentrant
上面的函数是有错误的,可重入函数不能传递bit类型的变量。在多任务系统中,可重入函数也不要用全局变量,多个函数同时调用时可能会使变量出现多个值,但是在单任务系统中,个人认为某些时候下是可以利用的。只要不出现改变变量值的情况。
一、可重入函数
首先对重入函数进行一下说明。
可重入函数主要应用在多任务环境中,一个可重入的函数简单来说就是可以被中断的函数。也就是说这个函数执行的任何时刻中断它,转入另一段代码,返回控制时不会出现什么错误,而不可重入的函数由于使用了系统资源,比如全局向量、中断向量表等,如果函数被中断的话可能会发生错误。
在Keil手册中对可重入函数的解释为:一个可重入函数可以在同一时间被几个进程共享。当一个函数可重入运行时,别的进程可中断执行,并开始执行相同的可重入函数。正常情况,C51编译器重的函数不能重入。原因是函数的参数和局部变量保存在固定的存储区中。通过reentrant函数属性允许声明函数可重入,因此可重复调用。可重入函数可以被递归调用,可同时被两个或多个进程调用。可重入函数经常在实时应用或者在中断和非中断必须共用一个函数的情况下被使用。如果函数定义为属性reentrant,那么这个可重入函数,一个可重入的堆栈区同时在内部和外部存储区模拟,这是由存储模式来决定的。如果是SMALL模式,则在idata存储区模拟可重入堆栈。如果是COMPACT模式,那么在pdata存储区模拟可重入函数堆栈。如果是LARGE模式可重入函数在xdata存储区模拟可重入堆栈。
二、可重入函数与函数的可重入
对此的详细解释引自:http://www.keil.com/support/docs/1873.htm
可重入函数与函数的可重入是两个不同的概念。
在C51中如果我们定义以下函数:
int function(int a, int b, int c) compact reentrant
{
long x, y, z;
....
}
由于声明为reentrant属性,因此函数为可重入函数,其参数(a,b,c)和局部变量(x,y,z)存储在模拟堆栈(simulated stack),由于是compact模式,因此在pdata区。
如果没有特意的声明compact,则会默认的为small,会在idata区模拟堆栈。如果是large则会在xdata区。这是可重入函数。(单片机的“硬件栈”,其实只有一个,就是我们通常说的SP,它是在内部RAM中的)
但有些函数未声明为reentrant,但是可重入的。大多是以汇编来编写的,其参数和局部变量存储在寄存器中(data),在C51的库函数中有很多这样的函数,它们是可重入的,但未用reentrant声明。
三、解释
普通的函数的形参和局部变量的存储是存在全局变量区(在《全局变量和局部变量存储》中详细的讲解),在递归调用的时候上一层次的局部变量会被本层次调用冲掉。通过reentrant,编译器会形成模拟栈为形参和局部变量分配内存。如果函数递归或者嵌套的次数太多,也会发生栈溢出(对于该模拟栈的大小可以在STARTUP.A51中修改)。
对于重入函数的模拟栈与单片机内的栈不同,模拟栈是由最顶端往下递减的,而sp则是grow up的。
在函数的递归或者通过函数指针调用函数时,如果被调用的函数中有字符串常量,有时会提示“WARNING 13: RECURSIVE CALL TO SEGMENT”
其具体的解决方法见转载文章“Keil "RECURSIVE CALL TO SEGMENT"彻底解决”。
上一篇:Keil C51编译及连接技术
下一篇:KEIL C51中const和code的使用
推荐阅读最新更新时间:2024-03-16 15:25