这几天在编写、调试基于MCU51的代码程序时,Keil在编译链接完毕后,弹出两个警告,其中一个如下,而另一个则与之类似:
*** WARNING L13: RECURSIVE CALL TO FUNCTION
CALLED: ?CO?LEDMENU_PROCESS
CALLER: _CARGOSTOCK_LEDMENU_SET/LEDMENU_PROCESS
通过查询,发现该警告表达的意思是,此处存在递归调用,被调用的是?CO?,调用者是_CARGOSTOCK_LEDMENU_SET函数。
这里首先来弄明白什么是"?CO?",因为在本程序代码中,并无一个名为"?CO?"的函数,
而通过查询(Keil Help -> Segment Naming Conventions)得知,所谓?CO?,在Keil中指的是一种数据段,存放的是Constant Data,
类型是:code,也就是说,这个数据段是被Keil安排在MCU的代码区的,实际查看编译生成的MAP文件,有如下片段:
START STOP LENGTH ALIGN RELOC MEMORY CLASS SEGMENT NAME
=========================================================================
* * * * * * * * * * * C O D E M E M O R Y * * * * * * * * * * * * *
......
008220H 008226H 000007H BYTE UNIT CODE ?PR?_LEDMENU_SUBFUNC_10?LEDMENU_PROCESS
008227H 00822DH 000007H BYTE UNIT CODE ?PR?_LEDMENU_SUBFUNC_11?LEDMENU_PROCESS
.....
00B46FH 00B4C9H 00005BH BYTE UNIT CONST ?CO?LEDMENU_PROCESS
......
由上可以知道const数据段是被安排在code memory(代码区)的,且集中安排在代码区的PR(可执行代码)段后面。
这里还应该注意到一点:在上面的函数在代码区映射中,每个函数的SEGMENT NAME均由三部分组成:Segment Prefix:?PR?;函数处理后的名称:_LEDMENU_SUBFUNC_10;函数所在文件:LEDMENU_PROCESS。而const代码则只有Segment Prefix声明:?CO?,和所属于的文件:LEDMENU_PROCESS,这点对于下面的问题分析很关键。
接下来打开.MAP文件,在文件开头有这样一段信息:
PROGRAM RECURSIONS REMOVED FROM CALL TREE
=========================================
+--> ?CO?LEDMENU_PROCESS
| | _LEDMENU_SUBFUNC_1/LEDMENU_PROCESS
<--+ _CARGOSTOCK_LEDMENU_SET/LEDMENU_PROCESS
这里软件就描述了Keil编译器所认为的递归调用关系,并根据之前对?CO?的连接,理出的情况应该是这样:?CO?代码段中,可以调用LEDMENU_SUBFUNC_1这个函数,
这是因为我在工程中定义了一个函数指针数组:
code void (*LedMenu_SubFunc[LED_MENU_ITEM_NUM])(LedMenu_KeyIndex_Enum KeyVal) =
{
LedMenu_SubFunc_1,
LedMenu_SubFunc_2,
LedMenu_SubFunc_3,
LedMenu_SubFunc_4,
LedMenu_SubFunc_5,
LedMenu_SubFunc_6,
LedMenu_SubFunc_7,
LedMenu_SubFunc_8,
LedMenu_SubFunc_9,
LedMenu_SubFunc_10,
LedMenu_SubFunc_11,
LedMenu_SubFunc_12,
LedMenu_SubFunc_13,
LedMenu_SubFunc_14,
LedMenu_SubFunc_15,
};
而这个函数指针数组还以“”code“”进行了位置声明,所以这个数组会被Kel处理并安排在const数据段【】。
而在LED_MENU_SUBFUNC_1函数中,调用了CARGOSTOCK_LEDMENU_SET()函数:
static void LedMenu_SubFunc_1(LedMenu_KeyIndex_Enum KeyVal)
{
CargoStock_LedMenu_Set(0,KeyVal);
}
最后在CargoStock_LedMenu_Set()函数中,调用了const数据进行显示操作:
TM1628_DisplayChar(LedCharData_Space,LED_MENU_CHAR_DISPLAY_POS_2);
其中:code u8 LedCharData_Space = 0x40;
这就是实际的函数调用情况。实际来看,这并不能够成递归调用,但实际又被Keil这样识别,问题何在呢?
查询Keil Help->Recursive Code References,看到有给出一个类似例子,并予以了说明:
void func1(unsigned char *msg ) { ; }
void func2( void ) {
unsigned char uc;
func1("xxxxxxxxxxxxxxx");
}
code void (*func_array[])() = { func2 };
void main( void ) {
(*func_array[0])();
}
编译该代码后,同样出现了类似警告:
*** WARNING 13: RECURSIVE CALL TO SEGMENT
SEGMENT: ?CO?EXAMPLE1
CALLER: ?PR?FUNC2?EXAMPLE1
对此,文章是这样解释的:
In this program example, func2 defines a constant string ("xxx...xxx") which is directed into the constant code segment ?CO?EXAMPLE1. The definitioncode void (*func_array[])() = { func2 }; yields a reference between segment?CO?EXAMPLE1 (where the code table is located) and the executable code segment?PR?FUNC2?EXAMPLE1. Because func2 also refers to segment?CO?EXAMPLE1, BL51 assumes that there is a recursive call.
归纳总结几点如下:
1、以code声明的对象(数据)都会被C51编译器安排到?CO?数据段里;
2、根据第一点,那么以code声明的函数指针数组也是存放在?CO?区域的,所以通过函数指针来调用函数,会先到?CO?区域找到函数地址,再跳转到?PR?区域执行函数;
3、Keil认为从?CO?段出发,如果层层调用,最后又调用了?CO?段代码,则认为存在递归调用。对于?CO?数据段是如此的,虽然还没找到确切的正式的文件说明。
4、一般我们理解的递归调用是FunA调用FunB,FunB又调用了FunA,相当于一个头尾相连的逻辑结构,但这是对于存储的?PR?的代码而言,亦即是对函数而言,对const数据则
认为均在一起。
另外,在uVision Help->In a ROM Variable中,讲述的例子,和当前面临的情况非常一致(详见:http://www.keil.com/support/man/docs/lx51/lx51_ol_fpromvar.htm):
因为其中的一个函数是通过code声明的函数指针数组变量来调用的,所以程序在编译、链接时,比起一般的函数内再调用其它函数,多了一个先去CO数据段寻找函数地址,再转到待执行函数的过程。这一点在工程生成的.MAP文件中的"OVERLAY MAP OF MODULE"也可看到。
总而言之,在Keil中,如果通过声明为code的函数指针数组来调用函数,则在编译、链接代码时,会生成相关代码先去访问?CO?数据段,再访问相关函数。而Keil C51是不能识别每次访问的是不是?CO?中的同一个数据,所以我所遇到的情况,原因就在于在:通过code的函数指针数组调用的函数中,存在再次调用?CO?数据段数据的现象。
要解决这个问题,Keil Help手册给的建议是,使用编译指示符,手动指定其递归调用关系。
在Keil开发环境中,Project窗口,右键点击Target,选择"Option Fo Target",在LX51(BX51) Misc栏目中,Overlay栏目加入编译控制命令,如下:
具体作用如下:
1)、CARGOSTOCK_LEDMENU_SET ~ ?CO?LEDMENU_PROCESS, //声明函数CARGOSTOCK_LEDMENU_SET与?CO?LEDMENU_PROCESS没有递归调用关系(实际上是CARGOSTOCK_LEDMENU_SET函数引用了?CO?数据段的数据对象)
2)、LEDMENU_SUBFUNC_14 ~ ?CO?LEDMENU_PROCESS //声明函数LEDMENU_SUBFUNC_14与?CO?LEDMENU_PROCESS没有递归调用关系
添加此两句控制命令后,再进行编译就不会有递归调用相关的警告了。
最后,再观察修改前的.MAP文件相关区域报告:
LEDMENU_EXECUTE/LEDMENU ----- ----- ----- -----
+--> ?CO?LEDMENU_PROCESS
+--> ?CO?LEDMENU
+--> ?PR?_LEDMENU_HALSHOW?LEDMENU_PROCESS
?CO?LEDMENU_PROCESS ----- ----- ----- -----
+--> ?PR?_LEDMENU_SUBFUNC_1?LEDMENU_PROCESS
+--> ?PR?_LEDMENU_SUBFUNC_2?LEDMENU_PROCESS
+--> ?PR?_LEDMENU_SUBFUNC_3?LEDMENU_PROCESS
+--> ?PR?_LEDMENU_SUBFUNC_4?LEDMENU_PROCESS
+--> ?PR?_LEDMENU_SUBFUNC_5?LEDMENU_PROCESS
+--> ?PR?_LEDMENU_SUBFUNC_6?LEDMENU_PROCESS
+--> ?PR?_LEDMENU_SUBFUNC_7?LEDMENU_PROCESS
+--> ?PR?_LEDMENU_SUBFUNC_8?LEDMENU_PROCESS
+--> ?PR?_LEDMENU_SUBFUNC_9?LEDMENU_PROCESS
+--> ?PR?_LEDMENU_SUBFUNC_10?LEDMENU_PROCESS
+--> ?PR?_LEDMENU_SUBFUNC_11?LEDMENU_PROCESS
LX51 LINKER/LOCATER V4.41 03/20/2017 10:48:28 PAGE 17
+--> ?PR?_LEDMENU_SUBFUNC_12?LEDMENU_PROCESS
+--> ?PR?_LEDMENU_SUBFUNC_13?LEDMENU_PROCESS
+--> ?PR?_LEDMENU_SUBFUNC_14?LEDMENU_PROCESS
+--> ?PR?_LEDMENU_SUBFUNC_15?LEDMENU_PROCESS
_LEDMENU_SUBFUNC_1/LEDMENU_PROCESS ----- ----- ----- -----
+--> ?PR?_CARGOSTOCK_LEDMENU_SET?LEDMENU_P...
_CARGOSTOCK_LEDMENU_SET/LEDMENU_PROCESS ----- ----- 0008H 000AH
+--> ?PR?_EEPROM_UPDATE?EEPROM_STC51
+--> ?PR?_TM1628_DISPLAYSTRING?TM1628_API
+--> ?PR?_EEPROM_NBYTEREAD?EEPROM_STC51
+--> ?PR?_CARGOBOX_CONTROL?APP
+--> ?PR?_TM1628_NUMTOCHAR?TM1628_API
+--> ?PR?_TM1628_DISPLAYCHAR?TM1628_API
修改后的.MAP文件相关区域报告:
LEDMENU_EXECUTE/LEDMENU ----- ----- ----- -----
+--> ?CO?LEDMENU_PROCESS
+--> ?CO?LEDMENU
+--> ?PR?_LEDMENU_HALSHOW?LEDMENU_PROCESS
?CO?LEDMENU_PROCESS ----- ----- ----- -----
+--> ?PR?_LEDMENU_SUBFUNC_1?LEDMENU_PROCESS
+--> ?PR?_LEDMENU_SUBFUNC_2?LEDMENU_PROCESS
+--> ?PR?_LEDMENU_SUBFUNC_3?LEDMENU_PROCESS
+--> ?PR?_LEDMENU_SUBFUNC_4?LEDMENU_PROCESS
+--> ?PR?_LEDMENU_SUBFUNC_5?LEDMENU_PROCESS
+--> ?PR?_LEDMENU_SUBFUNC_6?LEDMENU_PROCESS
+--> ?PR?_LEDMENU_SUBFUNC_7?LEDMENU_PROCESS
+--> ?PR?_LEDMENU_SUBFUNC_8?LEDMENU_PROCESS
+--> ?PR?_LEDMENU_SUBFUNC_9?LEDMENU_PROCESS
+--> ?PR?_LEDMENU_SUBFUNC_10?LEDMENU_PROCESS
+--> ?PR?_LEDMENU_SUBFUNC_11?LEDMENU_PROCESS
+--> ?PR?_LEDMENU_SUBFUNC_12?LEDMENU_PROCESS
+--> ?PR?_LEDMENU_SUBFUNC_13?LEDMENU_PROCESS
+--> ?PR?_LEDMENU_SUBFUNC_14?LEDMENU_PROCESS
+--> ?PR?_LEDMENU_SUBFUNC_15?LEDMENU_PROCESS
_LEDMENU_SUBFUNC_1/LEDMENU_PROCESS ----- ----- ----- -----
+--> ?PR?_CARGOSTOCK_LEDMENU_SET?LEDMENU_P...
_CARGOSTOCK_LEDMENU_SET/LEDMENU_PROCESS ----- ----- 0008H 000AH
+--> ?PR?_EEPROM_UPDATE?EEPROM_STC51
+--> ?PR?_TM1628_DISPLAYSTRING?TM1628_API
+--> ?PR?_EEPROM_NBYTEREAD?EEPROM_STC51
+--> ?PR?_CARGOBOX_CONTROL?APP
+--> ?PR?_TM1628_NUMTOCHAR?TM1628_API
+--> ?PR?_TM1628_DISPLAYCHAR?TM1628_API
其实,两者是完全一致的,说明Keil虽然产生了相关警告,但已经自动处理了该递归调用问题(.MAP文件起始部分):
PROGRAM RECURSIONS REMOVED FROM CALL TREE
=========================================
+--> ?CO?LEDMENU_PROCESS
| | _LEDMENU_SUBFUNC_1/LEDMENU_PROCESS
<--+ _CARGOSTOCK_LEDMENU_SET/LEDMENU_PROCESS
+--> ?CO?LEDMENU_PROCESS
<--+ _LEDMENU_SUBFUNC_14/LEDMENU_PROCESS
所以,对于这个问题归纳总结如下:
1、声明为code的对象会被Keil Lx51 Linker安排在相应文件的?CO?代码段。
2、通过code类型的函数指针来调用函数,Keil C51会生成如下代码流程:Caller ->?CO?->Func,所以就会访问到?CO?数据段;
3、通过函数指针调用的函数,Keil C51不能很好的进行处理,特别是函数中又引用了?CO?段数据,这种情况下进行程序编译,就会提示存在递归调用,但Keil会自动去掉的;
4、可通过OVERLAY命令,来手动指定递归调用关系;
http://blog.csdn.net/chungle2011/article/details/39104179