前几天在写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. sp_xml_preparedocument _使用 处理XML文档

      有时会在存储过程中处理一些XML格式的数据,所以会用到sp_xml_preparedocument,他可以将XML数据进行读取,然后使用 MSXML 分析器 (Msxmlsql.dll) 对其进行 ...

  2. bzoj2325 [ZJOI2011]道馆之战

    Description 口袋妖怪(又名神奇宝贝或宠物小精灵)红/蓝/绿宝石中的水系道馆需要经过三个冰地才能到达馆主的面前,冰地中的每一个冰块都只能经过一次.当一个冰地上的所有冰块都被经过之后,到下一个 ...

  3. 【POJ1338】Ugly Numbers(暴力打表)

    打表大军是一股邪恶势力→_→ #include <iostream> #include <cstring> #include <cstdlib> #include ...

  4. allVncClients

    VNC Viewer Free Edition 37  RealVNC Ltd.  15,367  Freeware  1021.58 KB VNC is client and server remo ...

  5. LinearLayout 和 RelativeLayout

    共有属性: java代码中通过btn1关联次控件 android:id="@+id/btn1" 控件宽度 android:layout_width="80px" ...

  6. swift 点击button改变其内填充图片,达到选中的效果

    先看下效果: 点击后: 实现:在页面拖一个button,然后在所在页面声明其变量和一个点击事件 声明: @IBOutlet weak var BtnZiDong: UIButton! 点击事件函数: ...

  7. HDU 2689 sort it - from lanshui_Yang

    Problem Description You want to processe a sequence of n distinct integers by swapping two adjacent ...

  8. Citrix 服务器虚拟化之十 Xenserver高可用性HA

    Citrix 服务器虚拟化之十 Xenserver高可用性HA HA是一套全自动功能设计,规划.它可以安全地恢复出现问题的XenServe 主机.例如物理破坏网络或主机的硬件故障,HA可确保无需任何人 ...

  9. Picasso – Android系统的图片下载和缓存类库

    Picasso – Android系统的图片下载和缓存类库 Picasso 是Square开源的一个用于Android系统下载和缓存图片的项目.该项目和其他一些下载图片项目的主要区别之一是:使用4.0 ...

  10. Javascript高级程序设计读书笔记(第六章)

    第6章  面向对象的程序设计 6.2 创建对象 创建某个类的实例,必须使用new操作符调用构造函数会经历以下四个步骤: 创建一个新对象: 将构造函数的作用域赋给新对象: 执行构造函数中的代码: 返回新 ...