×
嵌入式 > 嵌入式开发 > 详情

Keil C51 中的函数指针和再入函数

发布时间:2020-08-07 发布时间:
|
概述

函数指针是C语言中几个难点之一。由于8051的C编译器的独特要求,函数指针和再入函数有更多的挑战需要克服。主要由于函数变量的传递。

典型的(绝大部分8051芯片)函数变量通过堆栈的入栈和出栈命令来传递。因为8051只有有限的堆栈空间(128字节或更少的64字节),函数变量必须通过不同的方式进行传递。

8051的PL/M-51编译器,介绍在固定的存储空间存储变量的方式。当使用连接器时,程序建立一个调用树,计算出函数变量的互斥空间,然后覆盖它们。这就是连接器的“OVERLAY”指令。

因为PL/M-51不支持函数指针,所以不能实现间接函数调用。然而,C语言中存在这样的问题。连接器知道哪块空间用于存储间接函数的变量。怎样间接加入函数进入调用树?

本文解释在C51编程中,怎样有效使用函数指针。特别地,讨论如下几个话题:

分配常量地址给一个指针;

定义函数指针;

C51中函数指针问题;

使用OVERLAY指令确定调用树;

再入函数的指针;

固定地址的指针

你很容易的给函数指针分配一个数字地址。有许多原因需要这样做。例如,你需要复位目标。你可以设置函数指针为0000H去实现。

你可以使用标准C语言的类型映射特点,映射0X0000指针指向地址0的函数。例如,当你编译如下C代码….

((void (code *) (void))0x0000) ();

…编译器产生如下如下代码:

;FUNCTION main (BEGIN)

;SOURCELINE#3

0000120000LCALL00H

;SOURCELINE#4

000322RET

; FUNCTION main (END)

这正是我们期望的:LCALL0

把一个数字常量映射成一个函数指针是一件很复杂的事情。下面关于上面的函数调用的各部分的描述,将帮助你怎样更好的使用它们。

在上面的函数调用中,(void ( *) (void))是数据类型:一个不带参数且返回void的函数指针。

0x0000是一个映射地址。经过类型映射,函数指针指向地址0x0000。注意我们把一个圆括号放在数据类型和0x0000后面。如果我们仅仅想映射0x0000成为函数指针,这是不必要的。然而,因为我们将引用这个函数,这些圆括号是必要的。

映射一个数值常量成为指针和通过指针调用函数是不同的。为了实现这个,我们必须指定一个变量表。这就是为什么在此行的后面有一个()。

注意上面表达式中的所有圆括号都是必须的。分组和优先级是很重要的。

上面不带参数的函数指针和带参数的函数指针的唯一不同是数据类型和变量列表。例如,下面的函数调用…..

((long (code *) (int int int ) 0x8000)(1,2,3);

声明一个函数,地址在0x8000,接收3个int型参数,返回long型结果。

不带参数的函数指针

指向函数的函数指针是可变的。函数的地址是一个可变的数值。例如,下面的函数指针的声明….

void (*function_ptr) (void);

是一个调用function_ptr的指针。使用下面的代码调用function_ptr函数。

(*function_ptr ) ();

因为函数没有参数传送,所以参数列表时空的。

当定义变量的时候,函数指针可以被分配地址:void (*function_ptr) (void) = another_fuction;或者在程序执行过程中被分配,function_ptr = another_fuction;

注意,必须分配一个地址给函数指针。如果没有分配,函数指针将有一个0值(如果你运气好),或者有一些你完全不知道的数值,依赖于你的数据存储区的使用情况。当你间接的调用一个函数通过函数指针,如果函数指针没有初始化,你的程序将是混乱的。

为了声明一个带返回值的函数指针,在声明过程中你必须指定返回值的数据类型。例如,下面的声明改变了上面的函数指针的声明,返回一个float 数据。

float(*function_ptr) (void) = another_fuction;

带参数的函数指针

带参数的函数指针与不带参数的函数指针是相似的。例如:

void (*function_ptr) (int, long,char); 一个函数指针,带一个int参数,带一个long参数,带一个char参数。使用下面的代码调用函数。

(*function_ptr) (12, 34L,‘A’);

注意,函数指针仅仅可以指向小于等于3个参数的函数。这是因为,间接调用函数时,参数必须保存在寄存器中。关于超过3个参数的函数指针的信息,在再入函数中介绍。

使用函数指针的附加说明

如果你在C51中使用函数指针编程,有几个附加的说明你必须注意。

参数列表的限制

通过函数指针传递参数给函数必须把所有的参数存入寄存器。在大部分情况下,3个参数能够自动通过寄存器传递。在C51的用户手册中能找到传递参数进入寄存器的运算法则。但是并不保证,任何的3个数据类型可以传递。

因为C51在寄存器中传递3个参数,用于传递参数的存储空间是不被分配的,除非函数指向一个要求更多参数的函数。如果在那样的情况下,可以把参数混入一个结构体中,然后通过一个结构体指针传递参数。如果这样不可接受,你可以使用再入函数(看下面)。

调用树的保存

C51不把函数参数压栈(除非使用再入函数)。函数参数和全局变量被存入寄存器或固定的存储空间。这样阻止函数的再入。例如,一个函数调用它自己,它将覆盖它自己的参数或存储空间。函数的再入问题通过关键字“reentrant”来解决。函数指针的非再入函数的副作用,在执行中出现问题。

为了保护尽量多的数据空间,连接器执行调用树的性能分析,决定一些存储空间被安全的覆盖。例如,如果你的应用中包含main 函数,函数a,函数b,函数c,并且main函数调用a,b,c,但是a,b,c之间没有互相调用。在你应用中的调用树见出现如下:

MAIN

+→ A

+→ B

+→ C

这样A,B,C的存储空间可以被安全的覆盖。

当调用树不能正确的建立,函数指针将带来问题。因为连接器不能决定函数之间的引用。在这个问题上,没有自动的解决方法。



『本文转载自网络,版权归原作者所有,如有侵权请联系删除』

热门文章 更多
MSP430单片机硬件知识-复位