微波EDA网,见证研发工程师的成长!
首页 > 硬件设计 > 嵌入式设计 > Keil C51 中的函数指针和再入函数

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

时间:11-27 来源:互联网 点击:

调用树的保存

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

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

MAIN

+→ A

+→ B

+→ C

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

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

下面两个源文件将解答这个问题,使问题容易明白。第一个源文件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调用歌函数,连接映射文件出

Copyright © 2017-2020 微波EDA网 版权所有

网站地图

Top