本帖最后由 zlg315 于 2011-2-19 04:54 编辑
从简易微小内核说开去——全面阐述汇编与C的关系(3)
8.setjmp的实现
setjmp就是将相应的寄存器和返回地址保存到jmp_buf数组类型的jbBuf变量中,即保存的寄存器为变量bp的当前值、堆栈指针SP的当前值、高8位返回地址addr15~addr8和低8位返回地址addr7~addr0。对于80C51系列单片机来说,由于调用函数是使用ACALL或LCALL指令实现的,因此这些指令会将函数的返回地址保存在堆栈中,setjmp()定义详见程序清单1.3。
一般来说,未初始化的指针,实际上是非法的指针不能使用。但是未初始化的指针完全有可能指向任何地方,则程序无法判断它为非法指针。如果后续的代码忘记初始化pucBuf指针而直接使用它,则完全可能造成程序失败。虽然空指针也是非法指针,但可以通过程序判断后,告诉程序员代码可能有问题。也就是说,如果一开始就将指针初始化为空指针,则可避免程序异常。比如:
data unsigned char *pucBuf=(data void *)0; // 定义pucBuf为unsigned char类型指针并初始化为空指针
由于setjmp不需要在堆栈中保存其它的数据,因此仅需用程序清单1.3((46)、(47))保存返回地址即可,根据约定函数最后返回0(程序清单1.3(48))。
程序清单1.3 setjmp()定义(_setjmp.c)
30 extern unsigned char bp; // 编译器为简化重入操作而定义的变量
39 char setjmp (jmp_buf jbBuf)
40 {
41 data unsigned char *pucBuf=(data void *)0; // 指向上下文信息存储位置的指针
42
43 pucBuf = (data unsigned char *)jbBuf; // 将jbBuf数组的首地址赋给pucBuf
44 *pucBuf++ = bp; // 保存bp的当前值
45 *pucBuf++ = SP; // 保存SP的当前值
46 *pucBuf++ = *(( data unsigned char *)SP); // 保存返回地址的高8位
47 *pucBuf = *(( data unsigned char *)((char)(SP - 1)));// 保存返回地址的低8位
48 return 0;
49 }
下面不妨以“iRt = setjmp(jbTest);”为例,详细说明setjmp()的使用。其执行过程分两步,第一步,调用“setjmp()”函数,第二步,将函数的返回值赋给iRt变量。
setjmp()函数的执行过程详见图 1.1,分别用“实线”、“虚线”和“点划线”表示。对于SDCC51来说,编译器将调用“setjmp();”函数的语句编译成:
LCALL _setjmp;
“实线”表示此时各个成员指向的位置
当执行“LCALL _setjm”指令后,单片机将“返回地址”保存到单片机内部SP指向的idata位置,且跳转到程序清单1.3(43),使pucBuf指向jbBuf数组的首地址。
“虚线”表示setjmp()函数拷贝数据的过程
① 对应程序清单1.3(44),将bp的当前值保存到jbBuf中;
② 对应程序清单1.3(45),将单片机内部SP的值保存到jbBuf中;
③ 对应程序清单1.3((46)、(47)),将返回地址保存到jbBuf中。
对于SDCC51来说,“return 0”被SDCC编译成:
MOV DPL, #0x0 ;SDCC用DPL保存char类型返回参数
RET
“点划线”表示程序执行相应的拷贝步骤后,各个成员指向的新位置
⑵ 对应程序清单1.3(45),⑶ 对应程序清单1.3((46)、(47))。
然后将setjmp()的返回值保存到变量iRt中,即“iRt = setjmp();”。对于SDCC51来说,编译器将这条C语句编译成:
;参数赋值语句
LCALL _setjmp
MOV A, DPL ;SDCC用DPL保存char类型返回参数
“返回地址”指向“MOV A, DPL”这条语句,即“点划线⑶”指向的位置。 |