前几天在写C51程序时用到了递归,简单程序如下:

void WRITE_ADD(uchar addr,uchar wbyte)
{
     START();  //先发送起始信号
     WRITE_BYTE(0xa0); //设备地址+W命令
     if(!ERROR_Flag)  //正确收到应答
 {
      WRITE_BYTE(addr); //写入地址
 }
 else
 {
      ERROR_Flag = ;    //清错误标志
      WRITE_ADD(addr,wbyte);  //重新写入
 }
 if(!ERROR_Flag)     //地址收到正确应答
 {
       WRITE_BYTE(wbyte);   //发送要写入的数据
 }
 else
 {
      ERROR_Flag = ;    //清错误标志
     WRITE_ADD(addr,wbyte);  //重新写入
 }

 if(!ERROR_Flag)  //正确收到应答
 {
      STOP(); //停止
 }
 else
 {
      ERROR_Flag = ;    //清错误标志
      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"彻底解决”。

C51函数的递归调用的更多相关文章

  1. 你好,C++(27)在一个函数内部调用它自己本身 5.1.5 函数的递归调用

    5.1.5 函数的递归调用 在函数调用中,通常我们都是在一个函数中调用另外一个函数,以此来完成其中的某部分功能.例如,我们在main()主函数中调用PowerSum()函数来计算两个数的平方和,而在P ...

  2. day14 迭代器,生成器,函数的递归调用

    1.什么是迭代器 迭代是一个重复的过程,但是每次重复都是基于上一次重复的结果而继续 迭代取值的工具 2.为什么要用迭代器 迭代器的优点 ​ ①不依赖于索引取值 ​ ②更节省内存 缺点: ​ 1.不如按 ...

  3. c语言:函数的递归调用

    c语言可以将代码模块化,这是其很重要的一个特性. 说道代码模块化,我们很自然的就会联想到函数.而函数中,比较难的一个知识点就是函数的递归调用. 值得注意的是,函数的递归调用在现实工作并不是很常用,但是 ...

  4. [C++程序设计]函数的递归调用

    在调用一个函数的过程中又出现直接或间接地调用 该函数本身,称为函数的递归(recursive)调用. 包含递归调用的函数称为递归函数. 在实现递归时,在时间和空间上的开销比较大 求n! #includ ...

  5. Java中函数的递归调用

    说到递归,java中的递归和C语言中也是很相似的,在Java中,递归其实就是利用了栈的先进后出的机制来描述的. public class HelloWorld { public static void ...

  6. python--内置函数、匿名函数、递归调用

    匿名函数 有名函数: def func1(x): print(func1) 结果: <function func1 at 0x00000000005C3E18> 匿名函数: func2=l ...

  7. oracle数据库中函数的递归调用

    如有下面的表结构AAAA,用一个字段prev_id表示记录的先后顺序,要对其排序,需要用的递归函数 ID PREV_ID CONT 99   a 23 54 d 21 23 e 54 33 c 33 ...

  8. JavaScript Arguments 实现可变参数的函数,以及函数的递归调用

    //可变参数的函数 注:也可以使用对象作为参数来实现 function Max() { var temp = arguments[0] || 0; for (var i = 1; i < arg ...

  9. trampoline蹦床函数解决递归调用栈问题

    递归函数的调用栈太多,造成溢出,那么只要减少调用栈,就不会溢出.怎么做可以减少调用栈呢?就是采用"循环"换掉"递归". 下面是一个正常的递归函数. functi ...

随机推荐

  1. Activity被回收导致fragment的getActivity为null的解决办法

    这两天一直被这个问题困扰,假如app长时间在后台运行,再点击进入会crash,而且fragment页面有重叠现象,让我十分不爽.研究了一天,终于明白其中的原理并加以解决.解决办法如下: 如果系统内存不 ...

  2. cocos2d-x Loading界面实现资源加载

    有时候场景中的资源加载过多的话就会引起游戏进入的时候很卡,因为那是边加载边显示.在tests例子里面有一个很好的例子叫做TextureCacheTest,里面讲解了如何写loading. #inclu ...

  3. hdu1824-Let's go home:图论2-SAT

    关键在于找出一定矛盾的条件,设一队的3个人为(a,b,c),a为队长,那么(a不留下,b不留下)矛盾,(a不留下,c不留下)矛盾; 对于每一对队员,(a留下,b留下)矛盾. 把模型建好,剩下的就是套模 ...

  4. Kuhn-Munkres算法。带权二分图匹配模板 (bin神小改版本)

    /****************************************************** 二分图最佳匹配 (kuhn munkras 算法 O(m*m*n)). 邻接矩阵形式 . ...

  5. 【剑指offer】面试题37:两个链表的第一个公共结点

    题目: 输入两个链表,找出它们的第一个公共结点. 思路: 由链表的定义知是单链表.对于单链表,如果两个链表有公共结点,则两个链表必然是像Y型相交.则先计算出各个链表的长度,让长链表的头指针先走多出来的 ...

  6. Python闭包与函数对象

    1. Python闭包是什么 在python中有函数闭包的概念,这个概念是什么意思呢,查看Wikipedia的说明如下: “ In programming languages, closures (a ...

  7. WPF多线程问题

    最近碰到这种多线程问题都是在WPF项目中. 1. 问题是这样.有个一主界面线程,然后background线程启动,这个background线程试图去修改主界面里面的数据. 造成死锁. 调用过程,主界面 ...

  8. eclipse 添加resources 目录

    java项目需要一些配置,配置放置目录如:/src/main/resources; 如果没有这个文件夹,需要右键项目>new>source folder > Folder name ...

  9. editplus批量删除html代码空行

    在editplus替换菜单功能里,“查找”功能里输入: ^[ \t]*\n 替换为空,然后“全部替换”即可. 替换时,要选择“正则表达式”选项, 详细:http://www.dedecms8.com/ ...

  10. 如何在android上去控制开发进度

    这次android的壁纸软件1.0版本终于可以上线了,软件的功能基本上实现了,但是用户体验不太好.在整个开发阶段和测试阶段,出现了很多预料之外的事情,比如size是1M多的json文件解析.高清图片导 ...