现如下:
SEGMENTDATA_GROUP
+--> CALLED SEGMENTSTARTLENGTH
----------------------------------------------
?C_C51STARTUP----------
+--> ?PR?MAIN?FPT_MAIN
+--> ?C_INITSEG
?PR?MAIN?FPT_MAIN0008H0001H
?C_INITSEG----------
+--> ?PR?FUNC1?FP_TAB
+--> ?PR?FUNC2?FP_TAB
+--> ?PR?FUNC3?FP_TAB
?PR?FUNC1?FP_TAB0008H0008H
?PR?FUNC2?FP_TAB0008H0008H
?PR?FUNC3?FP_TAB0008H0008H
三个函数通过列表被调用,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空间。现在,连接映射文件如下:
SEGMENTDATA_GROUP
+--> CALLED SEGMENTSTARTLENGTH
----------------------------------------------
?C_C51STARTUP----------
+--> ?PR?MAIN?FPT_MAIN
?PR?MAIN?FPT_MAIN0008H0001H
+--> ?CO?FP_TAB
?CO?FP_TAB----------
+--> ?PR?FUNC1?FP_TAB
+--> ?PR?FUNC2?FP_TAB
+--> ?PR?FUNC3?FP_TAB
?PR?FUNC1?FP_TAB0009H0008H
?PR?FUNC2?FP_TAB0009H0008H
?PR?FUNC3?FP_TAB0009H0008H
现在,初始化代码中没有引入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.
IBPSTACKEQU0; set to 1 if small reentrant is used.
IBPSTACKTOPEQU0FFH+1; set top of stack to highest location+1.
;
;Stack Space for reentrant functions in the LARGE model.
XBPSTACKEQU0; set to 1 if large reentrant is used.
XBPSTACKTOPEQU0FFFFH+1; set top of stack to highest location+1.
;
;Stack Space for reentrant functions in the COMPACT model.
PBPSTACKEQU0; set to 1 if compact reentrant is used.
PBPSTACKTOPEQU0FFFFH+1; 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;