下面两个源文件将解答这个问题,使问题容易明白。第一个源文件FPCALLER.C,包括一个函数,它通过一个函数指针(fptr)调用另一个函数。
voidfunc_caller(long (code *fptr) (unsigned int))
{
unsigned char i;
for(i=0;i<10;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 = 0;
for (j = 0; j < count; j++)
{
k += j;
}
return (k);
}
void main (void)
{
func_caller (func);
while (1) ;
}
上面的两个的源文件编译和链接都没有错误。通过连接器,调用树的映射文件如下:
SEGMENTDATA_GROUP
+--> CALLED SEGMENTSTARTLENGTH
-------------------------------------------------
?C_C51STARTUP----------
+--> ?PR?MAIN?FPMAIN
?PR?MAIN?FPMAIN----------
+--> ?PR?_FUNC?FPMAIN
+--> ?PR?_FUNC_CALLER?FPCALLER
?PR?_FUNC?FPMAIN0008H000AH
?PR?_FUNC_CALLER?FPCALLER0008H0003H
在这个简单的例子中,许多信息可以从调用树里挖掘出来。?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”指令修正调用树,调整后的映射文件如下:
SEGMENTDATA_GROUP
+--> CALLED SEGMENTSTARTLENGTH
-------------------------------------------------
?C_C51STARTUP----------
+--> ?PR?MAIN?FPMAIN
?PR?MAIN?FPMAIN----------
+--> ?PR?_FUNC_CALLER?FPCALLER
?PR?_FUNC_CALLER?FPCALLER0008H0003H
+--> ?PR?_FUNC?FPMAIN
?PR?_FUNC?FPMAIN000BH000AH
修正后的调用树中,FUNC_CALLER函数和FUNC函数使用独立存储空间。
函数指针列表
下面是一个典型的函数指针列表的定义:
long (code *fp_tab []) (void) = { func1, func2, func3 };
如果你的MAIN函数中通过fp_tab调用歌函数,连接映射文件出现如下:
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就能正确的连接。
评论