Keil C51 中的函数指针和再入函数
函数指针是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) ; SOURCE LINE #3 LCALL 00H ;SOURCE LINE #4 RET ; FUNCTION main (END)
这正是我们期望的:LCALL 0
把一个数字常量映射成一个函数指针是一件很复杂的事情。下面关于上面的函数调用的各部分的描述,将帮助你怎样更好的使用它们。在上面的函数调用中,( void (*) (void) ) 是数据类型:一个不带参数且返回void的函数指针。0x0000是一个映射地址。经过类型映射,函数指针指向地址0x0000。注意我们把一个圆括号放在数据类型和0x0000后面。如果我们仅仅想映射0x0000成为函数指针,这是不必要的。然而,因为我们将引用这个函数,这些圆括号是必要的。映射一个数值常量成为指针和通过指针调用函数是不同的。为了实现这个,我们必须指定一个变量表。这就是为什么在此行的后面有一个()。
注意上面表达式中的所有圆括号都是必须的。分组和优先级是很重要的。
上面不带参数的函数指针和带参数的函数指针的唯一不同是数据类型和变量列表。例如,下面的函数调用:
( ( ,,);
声明一个函数,地址在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) (, 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的存储空间可以被安全的覆盖。
当调用树不能正确的建立,函数指针将带来问题。因为连接器不能决定函数之间的引用。在这个问题上,没有自动的解决方法。
下面两个源文件将解答这个问题,使问题容易明白。第一个源文件FPCALLER.C,包括一个函数,它通过一个函数指针(fptr)调用另一个函数。
void func_caller(long (code *fptr) (unsigned int)) { unsigned char i; ;i<;i++) { (*ftpr)(i); } }
第二个源文件FPMAIN.C,包含C主函数和被func_caller调用的函数func。注意main函数调用func_caller,把func的地址作为参数传递给func_caller。
extern void func_caller ( long (code *)(unsigned int) ); int func(unsigned int count) { long j; long k; k = ; ; j < count; j++) { k += j; } return (k); } void main (void) { func_caller (func); ) ; }
上面的两个的源文件编译和链接都没有错误。通过连接器,调用树的映射文件如下:
SEGMENT DATA_GROUP +--> CALLED SEGMENT START LENGTH ------------------------------------------------- ?C_C51STARTUP ----- ----- +--> ?PR?MAIN?FPMAIN ?PR?MAIN?FPMAIN ----- ----- +--> ?PR?_FUNC?FPMAIN +--> ?PR?_FUNC_CALLER?FPCALLER ?PR?_FUNC?FPMAIN 0008H 000AH ?PR?_FUNC_CALLER?FPCALLER 0008H 0003H
在这个简单的例子中,许多信息可以从调用树里挖掘出来。?C_C51STARTUP段调用main函数的?PR?MAIN?FPMAIN,段名各部分解析:PR是代码存储区,MAIN是函数名,FPMAIN是定义函数所在的源文件名。
MAIN函数调用FUNC和FUNC_CALLER(根据调用树)。注意这是错误的。MAIN函数没有调用FUNC函数,但是它传递FUNC函数的地址给FUNC_CALLER函数。同时注意,根据调用树FUNC_CALLER没有调用FUNC。这是因为FUNC_CALLER是通过函数指针间接调用FUNC。
FPMAIN文件中的FUNC函数使用从0008H开始,长000AH字节的数据。FPCALLER文件中的FUNC_CALLER函数也使用从0008H开始,长0003H字节的数据。这是重要的。
FUNC_CALLER函数使用的存储区从0008H开始,FUNC函数使用的存储区也是从0008H开始。因为FUNC_CALLER函数调用FUNC函数,又因为两个函数使用相同的存储区,这样就产生了问题。当FUNC函数被FUNC_CALLER函数调用时,存储区将被FUNC_CALLER破坏。这个问题是怎样产生的?是由Keil 51编译器产生还是由连接器产生?这个问题的原因是函数指针。当你使用函数指针时,你将总是遇到这样的问题。幸运的是,他们是容易被修改的。“OVERLAY”指令让你指定在调用树中,函数与其他函数是怎样连接的。
为了修正上面显示的调用树,FUNC函数必须从MAIN函数中删除,同时FUNC函数必须插入到FUNC_CALLER函数中。下面用“OVERLAY”指令修改后如下:
OVERLAY (?PR?MAIN?FPMAIN ~ ?PR?_FUNC?FPMAIN, ?PR?_FUNC_CALLER?FPCALLER ! ?PR?_FUNC?FPMAIN)
为了删除或插入相关的进入调用树,指定第一调用和第二调用。“~”符号用于删除相关的函数,“!”用于插入一个外部函数。例如?PR?MAIN?FPMAIN ~ ?PR?_FUNC?FPMAIN,意义是从MAIN函数中删除FUNC函数的调用。
经过调整连接命令,包括用“OVERLAY”指令修正调用树,调整后的映射文件如下:
SEGMENT DATA_GROUP +--> CALLED SEGMENT START LENGTH ------------------------------------------------- ?C_C51STARTUP ----- ----- +--> ?PR?MAIN?FPMAIN ?PR?MAIN?FPMAIN ----- ----- +--> ?PR?_FUNC_CALLER?FPCALLER ?PR?_FUNC_CALLER?FPCALLER 0008H 0003H +--> ?PR?_FUNC?FPMAIN ?PR?_FUNC?FPMAIN 000BH 000AH
修正后的调用树中,FUNC_CALLER函数和FUNC函数使用独立存储空间。
函数指针列表
下面是一个典型的函数指针列表的定义:
long (code *fp_tab[]) (void) = { func1, func2, func3 };
如果你的MAIN函数中通过fp_tab调用歌函数,连接映射文件出现如下:
SEGMENT DATA_GROUP +--> CALLED SEGMENT START LENGTH ---------------------------------------------- ?C_C51STARTUP ----- ----- +--> ?PR?MAIN?FPT_MAIN +--> ?C_INITSEG ?PR?MAIN?FPT_MAIN 0008H 0001H ?C_INITSEG ----- ----- +--> ?PR?FUNC1?FP_TAB +--> ?PR?FUNC2?FP_TAB +--> ?PR?FUNC3?FP_TAB ?PR?FUNC1?FP_TAB 0008H 0008H ?PR?FUNC2?FP_TAB 0008H 0008H ?PR?FUNC3?FP_TAB 0008H 0008H
三个函数通过列表被调用,FUNC1,FUNC2 和FUNC3被C_INITSEG调用。但是这是错误的,C_INITSEG按照常规的方式在程序中初始化。这些函数被引入初始化代码中,因为函数指针列表被初始化成这些函数的地址值。
注意这些变量(FUNC1,FUNC2 和FUNC13)和MAIN函数的起始地址都是0008H。这样不能正常工作,因为MAIN函数调用FUNC1,FUNC2 和FUNC3(通过函数指针类表)。
C51编译器和BL51连接器联合工作,当使用函数指针列表时,使得函数变量空间覆盖很容易。但是,你必须合理的声明指针列表。如果你这样做了,就可以避免使用“OVERLAY”指令。下面的函数指针列表的定义,C51和BL51可以自动处理:
code long (code *fp_tab[]) (void) = { func1, func2, func3 };
注意唯一不同的是存储列表在CODE空间。现在,连接映射文件如下:
SEGMENT DATA_GROUP +--> CALLED SEGMENT START LENGTH ---------------------------------------------- ?C_C51STARTUP ----- ----- +--> ?PR?MAIN?FPT_MAIN ?PR?MAIN?FPT_MAIN 0008H 0001H +--> ?CO?FP_TAB ?CO?FP_TAB ----- ----- +--> ?PR?FUNC1?FP_TAB +--> ?PR?FUNC2?FP_TAB +--> ?PR?FUNC3?FP_TAB ?PR?FUNC1?FP_TAB 0009H 0008H ?PR?FUNC2?FP_TAB 0009H 0008H ?PR?FUNC3?FP_TAB 0009H 0008H
现在,初始化代码中没有引入FUNC1,FUNC2 和FUNC3。但是,MAIN函数中引入一个常数段FP_TAB。这是一个函数指针列表。因为函数指针列表引入了FUNC1,FUNC2 和FUNC3,所以调用树是正确的。
只要把函数指针列表放在一个独立的源文件中,在调用树中,C51和BL51就能正确的连接。
函数指针的建议和技巧
有些函数指针的应用技巧。
使用指定空间的指针
把函数指针从一个普通的指针变成一个指定空间的指针。用一个字节保存指针。因为函数属于CODE存储区(在8051里),一个字节可以用来保存声明的函数指针作为CODE指针。例如:
void (code *function_ptr) (void) = another_function;
如果你选择在你的函数指针声明中包含code关键字,就可以在任何地方使用它。如果你声明一个函数,它接收一个3字节的普通指针,通过指定空间传递,2字节函数指针,坏事将要产生。
再入函数和指针
Keil C51 为函数的再入提供关键字“reentrant”。再入函数的参数通过模拟栈来传递。模拟栈对于small存储模式位于IDATA,对于compact存储模式位于PDATA,对于large存储模式位于XDATA。如果你使用再入函数,在STARTUP.A51中你必须初始化再入栈的指针。参考下面的启动代码:
;---------------------------------------------------------------------- ; Reentrant Stack Initilization ; ; The following EQU statements define the stack pointer for reentrant ; functions and initialized it: ; ; Stack Space for reentrant functions in the SMALL model. IBPSTACK EQU ; set to 1 if small reentrant is used. IBPSTACKTOP EQU 0FFH+ ; set top of stack to highest location+1. ; ; Stack Space for reentrant functions in the LARGE model. XBPSTACK EQU ; set to 1 if large reentrant is used. XBPSTACKTOP EQU 0FFFFH+ ; set top of stack to highest location+1. ; ; Stack Space for reentrant functions in the COMPACT model. PBPSTACK EQU ; set to 1 if compact reentrant is used. PBPSTACKTOP EQU 0FFFFH+ ; set top of stack to highest location+1. ;----------------------------------------------------------------------
你必须设置你使用的存储模式的堆栈和设置栈顶。当有入栈时,再入函数的栈指针减少(向下移动)。为了保护内部的数据区,有一个技巧就是把所有的再入函数放在一个独立的存储模式,像large或compact。
用reentrant声明再入函数。
void reentrant_func (long arg1, long arg2, long arg3) reentrant { }
用large和reentrant声明一个large模式的再入函数。
void reentrant_func (long arg1, long arg2, long arg3) large reentrant { }
声明一个再入函数的函数指针,必须使用reentrant关键字。
void (*rfunc_ptr) (long, long, long) reentrant = reentrant_func;
再入函数的函数指针和非再入函数的函数指针没有许多不同。当使用再入函数指针时,会生成更多的代码,因为参数被压入模拟栈。然而,没有特殊的连接要求和不需要打乱“OVERLAY”指令。如果通过间接调用传递超过3个参数给函数,需要再入函数指针。
总结:函数指针是非常有用的,并不是很困难的,如果你注意连接调用树,保证用“OVERLAY”指令修正一些冲突。
以下为原文,有问题可以参考原文。
Application Note
Function Pointers in C51 APNT_129
Page 1 of 10 Revision date: 27-Apr-99
OVERVIEW
Function pointers are one of the many difficult features of the C programming language. Due to
the unique requirements of a C compiler for the 8051 architecture, function pointers and
reentrant functions have even greater challenges to surmount. This is primarily due to the way
function arguments are passed.
Typically, (for most every chip other than the 8051) function arguments are passed on the stack
using the push and pop assembly instructions. Since the 8051 has a size limited stack (only 128
bytes and as low as 64 bytes on some devices), function arguments must be passed using a
different technique.
When Intel introduced the PL/M-51 compiler for the 8051, they introduced the technique of
storing arguments in fixed memory locations. When the linker was invoked, it built a call tree of
the program, figured out which function arguments were mutually exclusive, and overlaid them.
This was the beginning of the linker's OVERLAY directive.
Since PL/M-51 doesn't support function pointers, the issue of indirect function calls never came
up. However, with C, problems abound. How does the linker "know" which memory to use for
the indirect function's arguments? How do you add functions that are called indirectly into the
call tree?
This document explains how to effectively use function pointers in your C51 programs. Several
examples are used to illustrate the problems and solutions that are discussed. Specifically, the
following topics are discussed.
? Casting a Constant Address to a Pointer
? Declaring Function Pointers
? Problems with Function Pointers in C51
? Using the OVERLAY Directive to Fix the Call Tree
? Pointers to Reentrant FunctionsApplication Note
Function Pointers in C51 APNT_129
Page 2 of 10 Revision date: 27-Apr-99
POINTERS TO FIXED ADDRESSES
You can easily type cast numeric addresses into function pointers. There are numerous reasons
to do this. For example, you may need to reset the target and application without toggling the
reset line to the CPU. You can use a function pointer to address 0000h to accomplish this.
You may use the type casting features of Standard C to cast 0x0000 into a pointer to a function at
address 0. For example, when you compile the following line of C code...
((void (code *) (void)) 0x0000) ();
... the compiler generates the following:
; FUNCTION main (BEGIN)
; SOURCE LINE # 3
0000 120000 LCALL 00H
; SOURCE LINE # 4
0003 22 RET
; FUNCTION main (END)
This is exactly what we expected: LCALL 0.
Type casting a numeric constant into a function pointer is a tricky thing. The following
description of the components of the above function call will help you understand how to better
use them.
In the above function call, (void (*) (void)) is the data type: a pointer to a function that takes no
arguments and that returns a void.
The 0x0000 is the address to cast. After the type cast, the function pointer points to address
0x0000. Note that we put parentheses around the data type and the 0x0000. This is unnecessary
if we only want to cast 0x0000 to a function pointer. However, since we are going to invoke the
function, these parentheses are required.
Casting a numeric constant to a pointer is not the same as calling a function through a pointer.
To do that, we must specify an argument list. That is what the () at the end of the line does.
Note that all parentheses in this expression are required. Grouping and precedence are
important.
The only difference between the above pointer and a pointer to a function with arguments is the
data type and the argument list. For example, the following function call...
((long (code *) (int, int, int)) 0x8000) (1, 2, 3);
... invokes a function, at address 0x8000, that accepts three (3) int arguments and returns a long.Application Note
Function Pointers in C51 APNT_129
Page 3 of 10 Revision date: 27-Apr-99
POINTERS TO FUNCTIONS WITH NO ARGUMENTS
Function pointers are variables that point to a function. The value of the variable is the address
of the function. For example, the following function pointer declaration...
void (*function_ptr) (void);
...is a pointer called function_ptr. Use the following code to invoke the function that
function_ptr points to.
(*function_ptr) ();
Since the function that function_ptr points to takes no arguments, none are passed. That's why
the argument list is empty.
The address that function_ptr calls may be assigned when the variable is declared:
void (*function_ptr) (void) = another_function;
Or, it may be assigned during program execution, at run-time.
function_ptr = and_another_function;
It is important to note that you must assign an address to a function pointer. If you don't, the
pointer may have a value of 0 (if you're lucky) or it may have some completely undetermined
value, depending on how your data memory is used. If the pointer is uninitialized when you
indirectly call a function through it, your program will probably crash.
To declare a pointer to a function with a return value you must specify the return type in the
declaration. For example, the following declaration changes the above declaration to point to a
function that returns a float.
float (*function_ptr) (void) = another_function;
Pretty simple stuff. Just remember where the parentheses go and it's easy.
POINTERS TO FUNCTIONS WITH ARGUMENTS
Pointers to functions with arguments are similar to pointers to functions with no arguments. For
example:
void (*function_ptr) (int, long, char);
...is a pointer to a function that takes an int, a long, and a char as arguments. Use the following
to invoke the function that function_ptr points to.
(*function_ptr) (12, 34L, 'A');
Note that function pointers may only point to functions with three (3) arguments or less. This is
because the arguments of indirectly called functions must reside in registers. Refer to reentrant
functions below for information about pointers to functions that use more than three arguments.Application Note
Function Pointers in C51 APNT_129
Page 4 of 10 Revision date: 27-Apr-99
CAVEATS OF USING FUNCTION POINTERS
There are several caveats you must be aware of if you use function pointers in your C51
programs.
ARGUMENT LIST LIMITS
Arguments passed to functions through a function pointer must all fit into registers. At most
three (3) arguments can automatically be passed in registers. Refer to the C51 User's Manual to
find out the algorithm for fitting arguments into registers. Do not assume that just any three
argument data types will fit.
Since C51 passes up to three arguments in registers, the memory space used for passing
arguments is not an issue unless the function pointed to requires more arguments. If that is the
case, you can merge the arguments into a structure and pass a pointer to the structure. If that is
not acceptable, you may always use reentrant functions (see below).
CALL TREE PRESERVATION
The C51 tool chain does not push function parameters onto the stack (unless reentrant functions
are used). Instead, function parameters and automatic variables (locals) are stored in registers or
in fixed memory locations. This prevents functions from being reentrant. For example, if a
function calls itself, it overwrites its own arguments and/or locals when it calls itself. The
problem of reentrancy is addressed by the reentrant keyword (see below). Another side-effect
of non-reentrancy is that function pointers can, and usually do, present implementation problems.
In order to preserve as much data space as possible, the linker performs call tree analysis to
determine if some memory areas may be safely overlaid. For example, if your application
consists of the main function, function a, function b, and function c; and if main calls a, b, and c;
and if a, b, and c call no other functions (not even each other); then, the call tree for your
application appears as follows:
MAIN
+--> A
+--> B
+--> C
And, the memory used by A, B, and C may be safely overlaid.
The problem with function pointers comes when the call tree cannot be correctly constructed.
The reason is that the linker cannot determine which function a function pointer references.
There is no automatic way around this problem. However, there is a manual one, albeit a bit
cumbersome.Application Note
Function Pointers in C51 APNT_129
Page 5 of 10 Revision date: 27-Apr-99
The following two source files help illustrate the problem and make the solution easier to
understand. The first source file, FPCALLER.C, contains a function that calls another function
through a function pointer (fptr).
void func_caller (
long (code *fptr) (unsigned int))
{
unsigned char i;
for (i = 0; i < 10; i++)
{
(*fptr) (i);
}
}
The second source file, FPMAIN.C, contains the main C function as well as the function that is
called indirectly by func_caller (defined above). Note that main calls func_caller and passes the
address of func as an argument.
extern void func_caller (long (code *) (unsigned int));
int func (unsigned int count)
{
long j;
long k;
k = 0;
for (j = 0; j < count; j++)
{
k += j;
}
return (k);
}
void main (void)
{
func_caller (func);
while (1) ;
}
The above two source files compile with no errors. They also link with no errors. The
following call tree is produced in the map file created by the linker.
SEGMENT DATA_GROUP
+--> CALLED SEGMENT START LENGTH
-------------------------------------------------
?C_C51STARTUP ----- -----
+--> ?PR?MAIN?FPMAIN
?PR?MAIN?FPMAIN ----- -----
+--> ?PR?_FUNC?FPMAIN
+--> ?PR?_FUNC_CALLER?FPCALLER
?PR?_FUNC?FPMAIN 0008H 000AH
?PR?_FUNC_CALLER?FPCALLER 0008H 0003H
There is a lot of information that can be derived from the call tree even in this simple example.Application Note
Function Pointers in C51 APNT_129
Page 6 of 10 Revision date: 27-Apr-99
The ?C_C51STARTUP segment calls the MAIN C function this is the ?PR?MAIN?FPMAIN
segment. This components of this segment name may be decoded as: PR is the something in the
PRogram memory, MAIN is the name of the function, and FPMAIN is the name of the source
file where the function is defined.
The MAIN function calls FUNC and FUNC_CALLER (according to the call tree). Note that this
is not correct. MAIN never calls FUNC. But, it does pass the address of FUNC to
FUNC_CALLER. Also note that according to the call tree, FUNC_CALLER does not call
FUNC. This is because it is called indirectly through a function pointer.
The FUNC function in FPMAIN uses 000Ah bytes of DATA which starts at 0008h.
FUNC_CALLER in FPCALLER uses 0003h bytes of DATA which also starts at 0008h. This is
important!
FUNC_CALLER uses memory starting at 0008h and FUNC also uses memory starting at 0008h.
Since FUNC_CALLER invokes FUNC and since both functions use the same memory area we
have a problem. When FUNC gets called (by FUNC_CALLER) it trashes the memory used by
FUNC_CALLER. How did this happen? Don't the Keil C51 Compiler and Linker work?
The cause of the problem here is the function pointer. Whenever you use function pointers, you
will almost always have these types of problems. Fortunately, they are easy to fix. The
OVERLAY linker directive lets you specify how functions are linked together in the call tree.
To correct the call tree shown above, the call to FUNC must be removed from the MAIN
function and a call to FUNC must be inserted under the FUNC_CALLER function. The
following OVERLAY command does just that.
OVERLAY (?PR?MAIN?FPMAIN ~ ?PR?_FUNC?FPMAIN,
?PR?_FUNC_CALLER?FPCALLER ! ?PR?_FUNC?FPMAIN)
To remove or insert references to the call tree, specify the caller first and the callee second.
Tilde ('~') removes a reference or call and the exclamation point ('!') adds a reference or call. For
example, ?PR?MAIN?FPMAIN ~ ?PR?_FUNC?FPMAIN removes the call from MAIN to
FUNC.
After the linker command is adjusted to include the OVERLAY directive to correct the call tree,
the map file appears as follows:
SEGMENT DATA_GROUP
+--> CALLED SEGMENT START LENGTH
-------------------------------------------------
?C_C51STARTUP ----- -----
+--> ?PR?MAIN?FPMAIN
?PR?MAIN?FPMAIN ----- -----
+--> ?PR?_FUNC_CALLER?FPCALLER
?PR?_FUNC_CALLER?FPCALLER 0008H 0003H
+--> ?PR?_FUNC?FPMAIN
?PR?_FUNC?FPMAIN 000BH 000AH
And, the call tree is now correct and the variables for FUNC and FUNC_CALLER are now in
separate spaces (and are not overlaid).Application Note
Function Pointers in C51 APNT_129
Page 7 of 10 Revision date: 27-Apr-99
TABLES OF FUNCTION POINTERS
The following is a typical function pointer table definition:
long (code *fp_tab []) (void) =
{ func1, func2, func3 };
If your main C function calls functions through fp_tab, the link map appears as follows:
SEGMENT DATA_GROUP
+--> CALLED SEGMENT START LENGTH
----------------------------------------------
?C_C51STARTUP ----- -----
+--> ?PR?MAIN?FPT_MAIN
+--> ?C_INITSEG
?PR?MAIN?FPT_MAIN 0008H 0001H
?C_INITSEG ----- -----
+--> ?PR?FUNC1?FP_TAB
+--> ?PR?FUNC2?FP_TAB
+--> ?PR?FUNC3?FP_TAB
?PR?FUNC1?FP_TAB 0008H 0008H
?PR?FUNC2?FP_TAB 0008H 0008H
?PR?FUNC3?FP_TAB 0008H 0008H
The three functions called through the table, func1, func2, and func3, appear as though they are
called by ?C_INITSEG. However, this is incorrect. ?C_INITSEG is the routine that initializes
the variables in your program. These functions are referenced in the initialization code because
the function pointer table is initialized with the addresses of these functions.
Note that the starting area for variables used by the main C function as well as func1, func2, and
func3 all start at 0008h. This won't work because the main C function calls func1, func2, and
func3 (through the function pointer table). And, the variables used in func1, et. al. overwrite
those used in main.
The C51 Compiler and BL51 Linker work in combination to make overlaying the variable space
of function easy when you use tables of function pointers. However, you must declare the
pointer tables appropriately. If you do this, you can avoid using the OVERLAY directive. The
following is a function pointer table definition that C51 and BL51 can handle automatically:
code long (code *fp_tab []) (void) =
{ func1, func2, func3 };
Note that the only difference is storing the table in CODE space.Application Note
Function Pointers in C51 APNT_129
Page 8 of 10 Revision date: 27-Apr-99
Now, the link map appears as follows:
SEGMENT DATA_GROUP
+--> CALLED SEGMENT START LENGTH
----------------------------------------------
?C_C51STARTUP ----- -----
+--> ?PR?MAIN?FPT_MAIN
?PR?MAIN?FPT_MAIN 0008H 0001H
+--> ?CO?FP_TAB
?CO?FP_TAB ----- -----
+--> ?PR?FUNC1?FP_TAB
+--> ?PR?FUNC2?FP_TAB
+--> ?PR?FUNC3?FP_TAB
?PR?FUNC1?FP_TAB 0009H 0008H
?PR?FUNC2?FP_TAB 0009H 0008H
?PR?FUNC3?FP_TAB 0009H 0008H
Now there is no reference from the initialization code to func1, func2, and func3. Instead, there
is a reference from main to the constant segment FP_TAB. This is the function pointer table.
Since the function pointer table references func1, func2, and func3, the call tree is correct.
As long as the function pointer table is located in a separate source file, C51 and BL51 make all
the correct links in the call tree for you.
FUNCTION POINTER TIPS AND TRICKS
There are some function pointer tricks you can use to make things easier.
USE MEMORY-SPECIFIC POINTERS
Change the function pointer from a generic pointer to a memory-specific pointer. This saves one
(1) byte for each pointer. The examples used so far used declared generic function pointers.
Since functions only reside in CODE memory (on the 8051), one byte may be saved by declaring
the functions pointer as a code pointer. For example:
void (code *function_ptr) (void) = another_function;
If you choose to include the code keyword in your function pointer declarations, make sure you
use it everywhere. If you declare a function that accepts a generic 3-byte function pointer and
the pass it a memory-specific, 2-byte function pointer, bad things will happen!Application Note
Function Pointers in C51 APNT_129
Page 9 of 10 Revision date: 27-Apr-99
REENTRANT FUNCTIONS AND POINTERS
Keil C51 offers the reentrant keyword for functions that are reentrant. Reentrant functions
expect arguments to be passed on a simulated stack. The stack is maintained in IDATA for small
memory model, PDATA for compact memory model, or XDATA for large memory model. You
must initialize the reentrant stack pointer in STARTUP.A51 if you use reentrant functions. Refer
to the following excerpt from the startup code.
;----------------------------------------------------------------------
; Reentrant Stack Initilization
;
; The following EQU statements define the stack pointer for reentrant
; functions and initialized it:
;
; Stack Space for reentrant functions in the SMALL model.
IBPSTACK EQU 0 ; set to 1 if small reentrant is used.
IBPSTACKTOP EQU 0FFH+1 ; set top of stack to highest location+1.
;
; Stack Space for reentrant functions in the LARGE model.
XBPSTACK EQU 0 ; set to 1 if large reentrant is used.
XBPSTACKTOP EQU 0FFFFH+1 ; set top of stack to highest location+1.
;
; Stack Space for reentrant functions in the COMPACT model.
PBPSTACK EQU 0 ; set to 1 if compact reentrant is used.
PBPSTACKTOP EQU 0FFFFH+1 ; set top of stack to highest location+1.
;----------------------------------------------------------------------
You must set the equate for which memory model stack you use and set the top of the stack. The
reentrant stack pointer decreases (moves down) as items are pushed onto the stack. A neat trick
for conserving internal data memory is to place all reantrant functions in a separate memory
model like large or compact.
To declare a reentrant function, use the reentrant keyword.
void reentrant_func (long arg1, long arg2, long arg3) reentrant
{
}
To declare a large model reentrant function, use the large and reentrant keywords.
void reentrant_func (long arg1, long arg2, long arg3) large reentrant
{
}
To declare a function pointer to a reentrant function, you must also use the reentrant keyword.
void (*rfunc_ptr) (long, long, long) reentrant = reentrant_func;
There is not much difference between declaring reentrant function pointers and non-reentrant
function pointers.
When using pointers to reentrant functions, more code is generated because arguments must be
pushed onto the simulated stack. However, no special linker controls are required and you need
not mess with the OVERLAY directive.
If you pass more than three (3) arguments to a function that you call indirectly, reentrant function
pointers are required.Application Note
Function Pointers in C51 APNT_129
Page 10 of 10 Revision date: 27-Apr-99
CONCLUSION
Function pointers are useful and not too terribly difficult to use if you pay attention to the linker
call tree and make sure to use the OVERLAY directive to correct any inconsistencies.
Copyright ? 1999 Keil Software, Inc. All rights reserved.
In the USA: In Europe:
Keil Software, Inc. Keil Elektronik GmbH
16990 Dallas Parkway, Suite 120 Bretonischer Ring 15
Dallas, TX 75248-1903 D-85630 Grasbrunn b. Munchen
USA Germany
Sales: 800-348-8051 Phone: (49) (089) 45 60 40 - 0
Phone: 972-735-8052 FAX: (49) (089) 46 81 62
FAX: 972-735-8055
Keil C51 中的函数指针和再入函数的更多相关文章
- Keil C51中函数指针的使用
函数指针在C语言中应用较为灵活.在单片机系统中,嵌入式操作系统.文件系统和网络协议栈等一些较为复杂的应用都大量地使用了函数指针.Keil公司推出的C51编译器是事实上80C51 C编程的工业标准,它针 ...
- Keil C51中变量和函数的绝对地址定位问题
1.变量绝对地址定位 1) 在定义变量时使用 _at_ 关键字加上地址就可. unsigned char idata myvar _at_ 0x40; 把变量 myvar 定义在 idata 的 0 ...
- Keil C51 中指针的使用
指针是C语言中比较难的一个内容,Keil C51在指针方面有和标准C不一样的地方,今天看了一些资料学习了一下Keil C51 中指针的使用. keil51的指针,包含两种指针:普通指针,兼容标准C:内 ...
- keil c51中C程序的启动过程
汇编是从org 0000h开始启动,那么keil c51是如何启动main()函数的?keil c51有一个启动程序startup.a51,它总是和c程序一起编译和链接.下面看看它和main()函数是 ...
- Keil C51中变量的使用
引言 8051内核单片机是一种通用单片机,在国内占有较大的市场份额.在将C语言用于51内核单片机的研究方面,Keil公司做得最为成功.由于51内核单片机的存储结构的特殊性,Keil C51中变量的使用 ...
- [51单片机] Keil C51中变量的使用方法详解
引言 8051内核单片机是一种通用单片机,在国内占有较大的市场份额.在将C语言用于51内核单片机的研究方面,Keil公司做得最为成功.由于51内核单片机的存储结构的特殊性,Keil C51中变量 ...
- KEIL C51 中嵌入汇编以及C51与A51间的相互调用
如何在 KEIL C51(v6.21) 中调用汇编函数的一个示例 有关c51调用汇编的方法已经有很多帖子讲到,但是一般只讲要点,很少有对整个过程作详细描述,对于初学者是不够的,这里笔者通过一个简单例子 ...
- 关于Keil C51中using关键字的使用心得
刚才看到一位很牛的师兄写的一篇日志中提到了Keil C51中using这个关键字的用法,粗心的我本来一直都没有留意它是用来干嘛的(因为我一般看见它都是在中断服务函数的定义开头处,好像没有了它也可以中断 ...
- UNIX高级环境编程(13)信号 - 概念、signal函数、可重入函数
信号就是软中断. 信号提供了异步处理事件的一种方式.例如,用户在终端按下结束进程键,使一个进程提前终止. 1 信号的概念 每一个信号都有一个名字,它们的名字都以SIG打头.例如,每当进程调用了ab ...
随机推荐
- 数组在C++和java中的区别
几乎所有的程序设计语言都支持数组.在C和C++中使用数组是很危险的.因为C和C++中的数组就是内存块.如果一个程序要访问其自身内存块之外的数组,或者在数组初始化之前使用它,都会产生难以预料的后果. j ...
- Android ProgressDialog 加载进度
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools= ...
- C primer plus 读书笔记第二章
这章的标题是C语言概述,内容大概是介绍一些简单的示例程序,来了解和熟悉C语言的一些基本特征. 这是书里的第一段代码,敲敲找找感觉.推荐在linux环境下写代码. PS:倒腾sublime text一下 ...
- 利用MyEclipse的ant插件生成Hibernate的映射文件
先下载:xdoclet-plugins-dist-1.0.4-bin build.xml文件 <?xml version="1.0" encoding="UTF-8 ...
- 怎样在 SSASserver之间做同步
简单介绍: 从SQL Server 2005開始,分析服务就支持了同步的功能.本文将介绍怎样在SQL Server 2012下同步Adventureworks的分析服务数据库.通过同步的功能,我 ...
- 高性能 Socket 组件 HP-Socket v3.2.1-RC5 公布
HP-Socket 是一套通用的高性能 TCP/UDP Socket 组件,包括服务端组件.client组件和 Agent 组件,广泛适用于各种不同应用场景的 TCP/UDP 通信系统,提供 C/C+ ...
- ViewPager 详解(四)----自主实现滑动指示条
前言:前面我们用了三篇的时间讲述了有关ViewPager的基础知识,到这篇就要进入点实际的了.在第三篇<ViewPager 详解(三)---PagerTabStrip与PagerTitleStr ...
- Android - ContentProvider机制
以下资料摘录整理自老罗的Android之旅博客,是对老罗的博客关于Android底层原理的一个抽象的知识概括总结(如有错误欢迎指出)(侵删):http://blog.csdn.net/luosheng ...
- OKHttp源码解析
http://frodoking.github.io/2015/03/12/android-okhttp/ Android为我们提供了两种HTTP交互的方式:HttpURLConnection 和 A ...
- ubuntu 切换JDK版本
安装: 第一种方式:sudo apt-get install openjdk-7-jdk(or openjdk-6-jdk) 第二种方式:tar -zxvf jdk-7u79-linux-x64.ta ...