前几天在写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. 如何给开源的DUILib支持Accessibility(论述了DUILib的六个缺点,很精彩)

    最近的工作是给开源的DUILib支持Accessibility, 一些经验记录并分享下.   微软的Accessibility其实Windows平台上一个挺重要的东西, 尽管在国内不受重视,但是如果你 ...

  2. java实现二维码

    说起二维码,微信好像最先启用,随后各类二维码就开始流行起来了.那什么是二维码呢. 1.什么是二维码?百度一下即可 http://baike.baidu.com/view/132241.htm?fr=a ...

  3. dedecms 在php7.0无法安装

    dedecms 需要mysql扩展的支持!而php7.0已废弃mysql扩展.所以我讲7.0改回了5.6然后就可以顺利安装了. 总结了一个经验:没有绝对实力,不要尝试新东西

  4. PHP页面静态化(转)

    在很多地方都看到有PHP整站静态化的东东,怪唬人的..其实,你会静态化一个页面,那么别说整站了,想静态化多少都可以.所以关键是,首先要知道怎么静态化一个页面,了解静态化的原理是关键.. 这里就说下我个 ...

  5. 单片微机原理P4:80C51串口与串行总线拓展

    0. 串口通讯 0. 串口通讯的数据传输方式:单工(单向传输数据),半双工(非同时双向传输),全双工(同时,双向传输) 1. 根据通信方式的不同又分为同步通讯和异步通讯. 同步通讯:所有设备都使用同一 ...

  6. Spring 的优秀工具类盘点第 2 部分

    特殊字符转义 由于 Web 应用程序需要联合使用到多种语言,每种语言都包含一些特殊的字符,对于动态语言或标签式的语言而言,如果需要动态构造语言的内容时,一个我们经常会碰到的问题就是特殊字符转义的问题. ...

  7. sql加强练习

    1.用一条SQL语句 查询出每门课都大于80分的学生姓名 name kecheng fenshu 张三 语文 81张三 数学 75李四 语文 76李四 数学 90王五 语文 81王五 数学 100王五 ...

  8. dom4j解析接口使用SOAP传递的xml

    xml 文件的格式类型: <?xml version="1.0" encoding="utf-8"?> <SOAP-ENV:Envelope ...

  9. JSON解析---初识

    JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式 全然独立于语言的文本格式 易于人阅读和编写 易于解析和生成 (网络传输速度快) JSON语法规则 数据在 ...

  10. DotNet程序汉化过程--SnippetCompiler准确定位

    开篇前言 上一篇简单介绍了一下怎么汉化.Net程序,但那也仅仅是最基础的工作,要想汉化好一款软件基础我们得做扎实了,但是对于一些需要技巧的也不能不会啊,这一篇就介绍一下怎么准确定位字符串. 主要使用工 ...