前几天在写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. wpf只运行一个实例

    原文:wpf只运行一个实例 在winform下,只运行一个实例只需这样就可以: 1. 首先要添加如下的namespace: using System.Threading; 2. 修改系统Main函数, ...

  2. 【Xamarin 开发 IOS --IOS ViewController生命周期】

    ViewController ViewController是IOS开发中MVC模式中的C,ViewController是view的controller,ViewController的职责主要包括管理内 ...

  3. MySQL的SQL_CALC_FOUND_ROWS真的很慢么?

    分页程序一般由两条SQL组成: SELECT COUNT(*) FROM ... WHERE .... SELECT ... FROM ... WHERE LIMIT ... 如果使用SQL_CALC ...

  4. sql中update,alter,modify,delete,drop的区别和使用(整理)(转)

    关于update和alter: 百度知道上关于update和alter有一个很形象的总结: 一个表有很多字段,一个字段里有很多数据. 一个家有很多房间,一个房间里有很多家具. update是用来将衣柜 ...

  5. 图论:2-SAT模板

    #include<cstdio> #include<vector> #include<cstring> using namespace std; const int ...

  6. Lambda 表达式的示例-来源(MSDN)

    本文演示如何在你的程序中使用 lambda 表达式. 有关 lambda 表达式的概述,请参阅 C++ 中的 Lambda 表达式. 有关 lambda 表达式结构的详细信息,请参阅 Lambda 表 ...

  7. c语言typedef运用与函数指针

    #include <stdio.h> #include <stdlib.h> #define PINT int * typedef short* PSHORT; //typed ...

  8. c语言函数定义、函数声明、函数调用以及extern跨文件的变量引用

    1.如果没有定义,只有声明和调用:编译时会报连接错误.undefined reference to `func_in_a'2.如果没有声明,只有定义和调用:编译时一般会报警告,极少数情况下不会报警告. ...

  9. swift 随机数

    1.一行代码生成随机数  arc4random() 如果要生成一个生成在一定范围内的随机整数: func randomIn(#min: Int, max: Int) -> Int { retur ...

  10. SecureCRT 安装及初始化配置

    安装 SecureCRT 7.3.4 安装以及破解方法 SecureCRT 6.5.0 汉化解压版 初始化配置 这里配置以SecureCRT 6.5.0 汉化解压版为例 1.调整SecureCRT终端 ...