KeilC51 的Constant对象(CO)在调用时的递归问题

这几天在编写、调试基于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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值