_奶咖君_ 发表于 2023-12-27 12:30:13

zxcv1973 发表于 2023-12-27 12:24
他用的是STC32吧,不用模拟栈,性能损失可忽略。这种函数指针动态调用函数,想治本就得用可重入函数 ...

而C51里 维护调用树是治本,懒一点儿就不用这个功能就算了

zhangzhonghua 发表于 2023-12-27 12:50:13

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

确实是这个放错位置了,按你说的,就链接正常了。

zhangzhonghua 发表于 2023-12-27 12:51:51

_奶咖君_ 发表于 2023-12-27 12:28
啊 人用的C51 编译器 你是从那里看出来他用的STC32 ?

STC8H8K64U芯片,Lx51链接器。

_奶咖君_ 发表于 2023-12-27 13:02:54

zhangzhonghua 发表于 2023-12-27 12:50
确实是这个放错位置了,按你说的,就链接正常了。

恩 你可以再看看 变量的地址 应该就不一样了,,

zhangzhonghua 发表于 2023-12-27 13:08:18

zxcv1973 发表于 2023-12-27 12:24
他用的是STC32吧,不用模拟栈,性能损失可忽略。这种函数指针动态调用函数,想治本就得用可重入函数 ...

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

lzl1okOK 发表于 2023-12-27 14:27:08

估计是优化的问题

zxcv1973 发表于 2023-12-27 15:08:52

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

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

zhangzhonghua 发表于 2023-12-27 16:21:41

本帖最后由 zhangzhonghua 于 2023-12-28 11:08 编辑

zhangzhonghua 发表于 2023-12-27 13:08
我的看法:
1、可重入函数,根本出发点是解决循环调用、多入口(如main + 中断服务程序)调用的问题,方 ...
大功告成,来汇报下结果。

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千多字节。

zhangzhonghua 发表于 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了。

zhangzhonghua 发表于 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个改到中断服务程序去调用,那么调用关系就会改变,编译器识别得出来,会重新构建调用树,根据新调用树重新分配栈帧地址,不会重叠吧?


页: 1 2 3 [4] 5 6 7 8 9 10
查看完整版本: 遇到局部变量被意外修改的问题,发现是编译器给2个局部变量分配了相同的地址?