找回密码
 立即注册
楼主: zhangz***

遇到局部变量被意外修改的问题,发现是编译器给2个局部变量分配了相同的地址?

[复制链接]
  • TA的每日心情
    慵懒
    10 小时前
  • 签到天数: 137 天

    [LV.7]常住居民III

    20

    主题

    1051

    回帖

    2495

    积分

    金牌会员

    积分
    2495
    发表于 2023-12-27 12:30:13 | 显示全部楼层
    zxcv1973 发表于 2023-12-27 12:24
    他用的是STC32吧,不用模拟栈,性能损失可忽略。这种函数指针动态调用函数,想治本就得用可重入函数 ...

    而C51里 维护调用树是治本,懒一点儿就不用这个功能就算了
    参考例程并不是对技术参 考手册的补充,而是对技术参 考手册的解释。
    技术参 考手册不应该需要参考例程作为补充,而是解释成了参考例程的样子
    回复 支持 反对 送花

    使用道具 举报

    该用户从未签到

    4

    主题

    52

    回帖

    160

    积分

    注册会员

    积分
    160
     楼主| 发表于 2023-12-27 12:50:13 | 显示全部楼层
    _奶咖君_ 发表于 2023-12-27 12:09
    中间用英文逗号分隔,,,
    也不知道你百度了点啥,,哪个贴子教你这个东西要填在上面的,,,,直接喷那个 ...

    确实是这个放错位置了,按你说的,就链接正常了。
    回复 支持 反对 送花

    使用道具 举报

    该用户从未签到

    4

    主题

    52

    回帖

    160

    积分

    注册会员

    积分
    160
     楼主| 发表于 2023-12-27 12:51:51 | 显示全部楼层
    _奶咖君_ 发表于 2023-12-27 12:28
    啊 人用的C51 编译器 你是从那里看出来他用的STC32 ?

    STC8H8K64U芯片,Lx51链接器。
    回复 支持 反对 送花

    使用道具 举报

  • TA的每日心情
    慵懒
    10 小时前
  • 签到天数: 137 天

    [LV.7]常住居民III

    20

    主题

    1051

    回帖

    2495

    积分

    金牌会员

    积分
    2495
    发表于 2023-12-27 13:02:54 | 显示全部楼层
    zhangzhonghua 发表于 2023-12-27 12:50
    确实是这个放错位置了,按你说的,就链接正常了。

    恩 你可以再看看 变量的地址 应该就不一样了,,
    参考例程并不是对技术参 考手册的补充,而是对技术参 考手册的解释。
    技术参 考手册不应该需要参考例程作为补充,而是解释成了参考例程的样子
    回复 支持 反对 送花

    使用道具 举报

    该用户从未签到

    4

    主题

    52

    回帖

    160

    积分

    注册会员

    积分
    160
     楼主| 发表于 2023-12-27 13:08:18 | 显示全部楼层
    zxcv1973 发表于 2023-12-27 12:24
    他用的是STC32吧,不用模拟栈,性能损失可忽略。这种函数指针动态调用函数,想治本就得用可重入函数 ...

    我的看法:
    1、可重入函数,根本出发点是解决循环调用、多入口(如main + 中断服务程序)调用的问题,方法是每次调用都生成栈帧,还要用reentrant这个非标准C的扩展关键字。
    2、而我这个问题,根本原因是用了函数指针调用,而编译器不能正确理出调用树,又要Overlay去节省空间,结果搞得调用函数和被调用函数的局部变量地址重叠了。
    3、所以,最合适的,应该是维护调用树,其次,是不进行Overlay。我接下来都试下,对比效果。
    回复 支持 反对 送花

    使用道具 举报

  • TA的每日心情
    开心
    昨天 05:55
  • 签到天数: 128 天

    [LV.7]常住居民III

    4

    主题

    234

    回帖

    870

    积分

    高级会员

    积分
    870
    发表于 2023-12-27 14:27:08 | 显示全部楼层
    估计是优化的问题
    回复 支持 反对 送花

    使用道具 举报

    该用户从未签到

    1

    主题

    117

    回帖

    643

    积分

    高级会员

    积分
    643
    发表于 2023-12-27 15:08:52 | 显示全部楼层
    zhangzhonghua 发表于 2023-12-27 13:08
    我的看法:
    1、可重入函数,根本出发点是解决循环调用、多入口(如main + 中断服务程序)调用的问题,方 ...

    STC8不熟,要是STC32的话,最省事的解决办法就是所有函数都为可重入函数(工程配置里有一个选项勾上即可),可避免很多意想不到的BUG
    回复 支持 反对 送花

    使用道具 举报

    该用户从未签到

    4

    主题

    52

    回帖

    160

    积分

    注册会员

    积分
    160
     楼主| 发表于 2023-12-27 16:21:41 | 显示全部楼层
    本帖最后由 zhangzhonghua 于 2023-12-28 11:08 编辑
    zhangzhonghua 发表于 2023-12-27 13:08
    我的看法:
    1、可重入函数,根本出发点是解决循环调用、多入口(如main + 中断服务程序)调用的问题,方 ...

    大功告成,来汇报下结果。
    overlay对比.PNG
    1、出错时的情况:Program Size: data=10.2 xdata=633 const=1084 code=13940,
    问题:因为使用了函数指针,导致编译器不能识别这种调用关系,又要做Overlay,所以调用函数和被调函数的局部变量地址重叠,意外修改了。

    2、不做Overlay的情况:Program Size: data=16.3 xdata=980 const=1116 code=18385,
    优劣:调用函数和被调函数的局部变量地址不会重叠了。但未用代码没移除,占空间。xdata,const增加几百字节,而code增加了几千字节。

    3、手工维护调用树的情况:Program Size: data=10.2 xdata=633 const=1084 code=13940,
    优劣:调用函数和被调函数的局部变量地址不会重叠了,且空间占用和情况1时一样少。但需手工维护调用树,好在只需增加函数指针调用部分即可。这个是完美解决方案。

    12.28补充:
    4、C51编译器优化级别设为0的情况:Program Size: data=10.2 xdata=797 const=1084 code=16126
    优劣:比情况3多占一点空间,xdata增加164字节,code增加2千多字节。

    overlay对比.PNG

    点评

    三论 楼主38楼实验结果造假 (1)从11楼起到37楼,楼主和网友们交流了在C51编译器中用维护函数调用树的方法 来解决由于OVERLAY产生的局部变量地址重合的问题 的方法。最后楼主在38楼宣告“大功告成”的实验结果,还  详情 回复 发表于 2023-12-30 20:57
    回复 支持 反对 送花

    使用道具 举报

    该用户从未签到

    4

    主题

    52

    回帖

    160

    积分

    注册会员

    积分
    160
     楼主| 发表于 2023-12-27 16:31:21 | 显示全部楼层
    本帖最后由 zhangzhonghua 于 2023-12-27 16:40 编辑

    怎么维护调用树?
    原来以为麻烦,后来一看,挺简单的。
    比如processEvent()通过函数指针调用了proc_Up()、proc_Down(),编译器识别不出来这种调用关系,那么你告诉编译器就好了,
    在Overlay的框里加  processEvent ! proc_Up,processEvent ! proc_Down
    这样,Lx51命令行会出 OVERLAY(processEvent ! proc_Up,processEvent ! proc_Down)指示,链接器就能正确做Overlay了。
    回复 支持 反对 送花

    使用道具 举报

    该用户从未签到

    4

    主题

    52

    回帖

    160

    积分

    注册会员

    积分
    160
     楼主| 发表于 2023-12-27 17:34:54 | 显示全部楼层
    本帖最后由 zhangzhonghua 于 2023-12-28 12:01 编辑

    怎么观察具体修改效果的?

    看链接器生成的MAP文件。MAP文件主要5个部分。
    1、ACTIVE MEMORY CLASSES,例如
    BASE        START       END         USED      MEMORY CLASS
    ==========================================================
    C:000000H   C:000000H   C:00FFFFH   003674H   CODE
    X:000000H   X:000000H   X:00FFFFH   000279H   XDATA
    C:000000H   C:000000H   C:00FFFFH   00043CH   CONST
    I:000020H.0 I:000020H.0 I:00002FH.7 000001H.2 BIT
    I:000000H   I:000000H   I:00007FH   000008H   DATA
    I:000000H   I:000000H   I:0000FFH   000001H   IDATA
    这里,把code、xdata、const、data等区域的位置和大小都列出来了。

    2、MEMORY MAP,又分成4小部分,
    (1)  D A T A   M E M O R Y,这里是data区域的具体分配。
    (2)  C O D E   M E M O R Y,这里是code区域的具体分配。
    (3)X D A T A   M E M O R Y,这里是xdata区域的具体分配。
    (4)  R E M O V E D     S E G M E N T S,这里是移除的段,REMOVEUNUSED的效果怎样,看这里。
    和Overlay相关的是X D A T A   M E M O R Y里面,有一段名字叫_XDATA_GROUP_,例如,
    00021DH   000241H   000025H   BYTE   UNIT     XDATA          _XDATA_GROUP_
    这是什么?
    就是编译器给函数局部变量安排的地方!局部变量都在这个区域里。怎么用的?看下面的OVERLAY MAP。

    3、OVERLAY MAP,例如
    TIMER71MSPROCESS/MAIN                      0228H 0234H
      +--> _PROCESSEVENT/GD
    这里,说明给Timer71mSProcess()函数,分配了0228H到0234H的地址(_XDATA_GROUP_的一部分)用于其局部变量,另外调用了processEvent()函数。
    _PROCESSEVENT/GD                           0235H 023BH
      +--> _PROC_UP/GD
      +--> _PROC_DOWN/GD
    这里,说明给processEvent()函数,分配了0235H到023BH的地址(也是_XDATA_GROUP_的一部分)用于其局部变量,另外调用了proc_Up()、proc_Down()函数。

    这里要重点注意了:Timer71mSProcess()的局部变量地址尾巴是0234H,其调用了processEvent(),而processEvent()的局部变量地址头是0235H,正好接到Timer71mSProcess()的尾巴上。
    说明了,(1)调用函数和被调函数,他们的局部变量地址是不重叠的!(2)被调函数的局部变量地址在调用函数的后面增长。
    咦,好熟悉,这做法和PC的函数Stack Frame栈帧不是很像吗?
    0228H~0234H,不就是Timer71mSProcess()的Stack Frame吗?
    0235H~023BH,不就是processEvent()的Stack Frame吗?
    就是的,所以,Keil C51把这个做法叫做“编译时栈帧”,相对的,我们通常PC的叫做“运行时栈帧”,
    因为,C51这种栈帧,是编译时就确定的,而PC的栈帧,是运行时才确定的。

    4、PUBLIC SYMBOLS,公共符号表。也能看到REMOVEUNUSED的效果。
    5、SYMBOL TABLE,各模块的符号表,包括私有的。

    上面说的栈帧,地址并不重叠,那么,Overlay怎么体现呢?
    还是看OVERLAY MAP,例如
    TIMER1SPROCESS/MAIN                        0228H 0233H
      +--> _PROCESSEVENT/GD
    这里,说明给Timer1SProcess()函数,分配了0228H到0233H的地址(也是_XDATA_GROUP_的一部分)用于其局部变量,另外调用了processEvent()函数。
    对比下,
    Timer1SProcess()函数,栈帧是0228H~0233H,
    Timer71mSProcess()函数,栈帧是0228H~0234H,
    它们的栈帧地址是重叠的,都是0228H开头,编译器不给这2个函数分配不重叠地址的栈帧,节省了空间,这就是Overlay。
    那么,不会出现意外修改问题吗?
    不会,因为它们之间没有调用关系!
    所以,总结:
    (1)没有调用关系的函数,其栈帧可能重叠即Overlay,目的是节省空间。
    (2)如果有调用关系的函数,其栈帧不能重叠,否则局部变量会被意外修改。
    (3)如果调用关系是用函数指针实现的,编译器不能识别,需手工维护调用树。

    等一下,Timer1SProcess()和Timer71mSProcess()的栈帧地址重叠,条件是它们之间没有调用关系。
    可是,如果不同的入口分支(如main和中断服务程序),分别调用了这2个函数,由于地址重叠,还是会出现意外修改的情况啊?
    这好像就是重入函数的题目了。
    12.28补充:再想想,似乎多虑了,没到重入函数的时候。现在Timer1SProcess()和Timer71mSProcess()的栈帧地址重叠,是因为它们都是由main()调用,如果把其中1个改到中断服务程序去调用,那么调用关系就会改变,编译器识别得出来,会重新构建调用树,根据新调用树重新分配栈帧地址,不会重叠吧?


    回复 支持 反对 送花

    使用道具 举报

    您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则

    QQ|手机版|深圳国芯人工智能有限公司 ( 粤ICP备2022108929号-2 )

    GMT+8, 2024-5-8 20:12 , Processed in 0.089672 second(s), 70 queries .

    Powered by Discuz! X3.5

    © 2001-2024 Discuz! Team.

    快速回复 返回顶部 返回列表