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

上一篇:jquery的Post方法$.post()


下一篇:函数parseQuery用于解析url查询参数