




已阅读5页,还剩10页未读, 继续免费阅读
版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
Keil C51 中的函数指针和再入函数,函数指针与overlay=调用树的保存 C51不把函数参数压栈(除非使用再入函数)。函数参数和全局变量被存入寄存器或固定的存储空间。这样阻止函数的再入。例如,一个函数调用它自己,它将覆盖它自己的参数或存储空间。函数的再入问题通过关键字“reentrant”来解决。函数指针的非再入函数的副作用,在执行中出现问题。MAIN函数调用FUNC和FUNC_CALLER(根据调用树)。用函数的指针来传递参数,调用树往往会出错。=转这篇文章是由Keil C51 的英文文档翻译过来的,很多语句都是根据自己的理解翻译的,肯定还有许多地方需要推敲。希望读者能吸取到有用的部分,不要被误解了,自己多理解。 Overlay修改用于数据覆盖的调用树。如果在用户程序里使用了函数指针,或者使用了像实时操作系统中调度器那样的跳转(k 指的是指针方式的调用吗?),那么,修改程序调用树将是很有必要的。混合编程:函数名 符号名 解释viod func(void) FUNC 无参数传递,或不含寄存器参数的函数名不作改变 转入目标文件中,名字只是简单地转为大写形式viod func(char) _FUNC 带寄存器参数的函数名加入“_”字符前缀,它表明 这类函数包含寄存器内的参数传递viod func(viod) reentrant _?FUNC “_?”表示可重入,它表明该行数包含栈内的参数传递。=将下面这篇的难点内容提到前面使用函数指针的附加说明 如果你在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)调用另一个函数。FPCALLER.C / 第一个源文件FPCALLER.C (文件名)void func_caller(long (code *fptr) (unsigned int)/ (函数名) unsigned char i; for(i=0;i10;i+) (*ftpr)(i); 第二个源文件FPMAIN.C,包含C主函数和被func_caller调用的函数func。 注意main函数调用func_caller,把func的地址作为参数传递给func_caller。FPMAIN.C / 第二个源文件FPMAIN.C (文件名)extern void func_caller (long (code *) (unsigned int);int func (unsigned int count) / (函数名) long j; long k; k = 0; for (j = 0; j 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 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.;- 你必须设置你使用的存储模式的堆栈和设置栈顶。当有入栈时,再入函数的栈指针减少(向下移动)。为了保护内部的数据区,有一个技巧就是把所有的再入函数放在一个独立的存储模式,像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”指令修正一些冲突。=概述函数指针是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 #30000 120000 LCALL 00H ;SOURCE LINE #40003 22 RET ; FUNCTION main (END) 这正是我们期望的:LCALL 0把一个数字常量映射成一个函数指针是一件很复杂的事情。下面关于上面的函数调用的各部分的描述,将帮助你怎样更好的使用它们。 在上面的函数调用中,(void ( *) (void))是数据类型:一个不带参数且返回void的函数指针。 0x0000是一个映射地址。经过类型映射,函数指针指向地址0x0000。注意我们把一个圆括号放在数据类型和0x0000后面。如果我们仅仅想映射0x0000成为函数指针,这是不必要的。然而,因为我们将引用这个函数,这些圆括号是必要的。 映射一个数值常量成为指针和通过指针调用函数是不同的。为了实现这个,我们必须指定一个变量表。这就是为什么在此行的后面有一个()。 注意上面表达式中的所有圆括号都是必须的。分组和优先级是很重要的。 上面不带参数的函数指针和带参数的函数指针的唯一不同是数据类型和变量列表。例如,下面的函数调用. (long (code *) (int int int ) 0x8000)(1,2,3);声明一个函数,地址在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) (12, 34L,A);注意,函数指针仅仅可以指向小于等于3个参数的函数。这是因为,间接调用函数时,参数必须保存在寄存器中。关于超过3个参数的函数指针的信息,在再入函数中介绍。=标题:keil利用函数指针调用超过3个参数的函数问题 发信站:水木社区(ThuJun2322:37:582011),站内 我想利用函数指针实现对超过3个参数的函数进行调用,但编译总是出现错误,代码如下: voidfunc(inta,intb,intc,intd) printf(infunc:%d,%d,%d,%dn,a,b,c,d)
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 高中数学概率统计讲义+练习含答案解析 17、超几何分布
- 2025年黑龙江省基层法律服务工作者执业核准考试全真冲刺试题及答案一
- 医药产业园产业链整合实施规划方案
- 组织部副部长述职报告
- 教育前台述职报告
- 莒县幼儿园招生工作总结
- 水泥基材料施工管理方案
- 2025年国际商务礼仪与跨文化沟通测试题及答案
- 智慧树知道网课《基础写作(铜陵学院)》课后章节测试答案
- 水库枢纽生态保护与环境修复实施方案
- 人教版四年级数学上册《课堂作业设计》全套
- TTT系列课程-结构化思考力
- Cpk 计算标准模板
- 封起DE日子博文 2006
- 锂离子电池生产安全讲座
- 画魂空手套无删减全文下载
- 主题教育苏轼生平介绍人物经历等PPT模板(内容完整)
- 眼科学-眼科检查(课件)
- 产品碳足迹课件
- 部编人教版六年级道德与法治上册全册教学课件
- 美国地图高清中文版
评论
0/150
提交评论