搜索
bottom↓
回复: 32

C51中 return语句在main()函数中的副作用追踪

[复制链接]

出0入0汤圆

发表于 2013-12-4 10:00:42 | 显示全部楼层 |阅读模式
先说结论:
1、        C51中mian()并不是被调用的,而是跳转进去的;
2、        在main()中调用return语句,将会从栈中弹出SP大小的数据到SP中,进而程序会运行到弹出数据指向的地址,C51中可能就是SP指针返回0x0000。

    昨天和我合作一个工程的同事在调试的时候,发现单片机程序会经常出现重启的现象,当时我认为可能是数组溢出、指针越界导致的程序跑飞。
    后来同事将有问题的语句定位到某个分支中的“return 0;”语句上,我一看这居然是在main()函数上使用了“return 0;”,认为程序会在调用这个语句后,
PC指针离开main(),继续执行main()函数后面的代码导致程序跑飞(同事估计是受到VC等PC编译环境的影响,汗~)。
    不过,回到坐位后却想起return对应的指令,会负责将栈中的值取出来赋值给PC指针,这样的话执行“return 0;”后应该返回到调用main()函数的地方。
    既然有疑问就动手吧,首先向同事拿到了出问题的工程,在main()开始的地方设置了两个“return 0;”(分别是第25行和32行)。
    然后进入仿真模式,如下图,可以观察到sp指针指向起始值0x07,PC指针指向0x0000,PC指针的长度为2。

图1 请无视main()里面的那个void~~
    在disassembly界面单步跟踪,发现了跳转到main()的地方,并且可以看到sp是被赋值为0x28的。(不过这LJMP只是跳转而已,看来没有调用main()的过程)

图2
    全速运行到图1中0x0003的断点后,看到的寄存器如下:

图3
    sp指针在之前已经被设置成0x28了,这时候如果有ret语句被执行的话,0x27和0x28地址RAM上的数据应该就是会被弹出到PC指针上的。
    现在观察下RAM中的情况:

图4
    不过因为这两个地址(0x27和0x28)上的数据都是0,所以单步可以发现执行反汇编窗口0x0008地址上的RET语句后PC指针又跑回0x0000上了,这就是重启的原因:

图5 单步到0x0008,黄色的箭头是PC指针

图6 指针返回到了0x0000,并且sp指针退了两格
    下面为了证明,执行RET指令后,SP指针指向的数据是退出函数后的位置,再加个实验,首先将图1中第25行的“return 0;”注释掉。

图7 即将调用MCU_Init()
    运行到调用MCU_Init()这步上,可以看到PC指针为0x02EF,而退出后应该是运行到0x02F2上,单步进入到MCU_Init()后,0x29和0x2A果然被写入了0x02F2(进入函数,SP首先会将PC
压入栈,PC指针自然是在0x29和0x2A上),并且可以看出SP指针的低位是先被压入栈的~

图8

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?注册

x

阿莫论坛20周年了!感谢大家的支持与爱护!!

曾经有一段真挚的爱情摆在我的面前,我没有珍惜,现在想起来,还好我没有珍惜……

出0入0汤圆

 楼主| 发表于 2013-12-4 10:28:00 | 显示全部楼层
结论说错了,是PC指针回到0x0000~~

出0入0汤圆

发表于 2013-12-4 11:00:22 | 显示全部楼层
学习了,非常不错

出0入0汤圆

发表于 2013-12-4 11:03:51 | 显示全部楼层
新版本的Keil C51 V9.52 在main 中使用 return x; 时会有警告

出0入0汤圆

发表于 2013-12-4 12:32:36 | 显示全部楼层
main函数中不是要有一个while(1)么, 怎么会返回回去呢?

出0入0汤圆

发表于 2013-12-4 12:42:33 | 显示全部楼层
没看懂什么意思。。。。。。

出0入0汤圆

 楼主| 发表于 2013-12-4 15:18:04 | 显示全部楼层
zjsx133 发表于 2013-12-4 12:32
main函数中不是要有一个while(1)么, 怎么会返回回去呢?

你试下在while(1)循环里调用return x;效果也会是一样的。

出0入0汤圆

 楼主| 发表于 2013-12-4 15:20:53 | 显示全部楼层
typedef 发表于 2013-12-4 11:03
新版本的Keil C51 V9.52 在main 中使用 return x; 时会有警告

在9.0版本上,定义成int main(),并且没有while(1)的话就报错:MAIN.C(28): warning C290: missing return value
定义成void main(),并且没有while(1)的话就不会报错。
在实验的时候发现,两者的代码没有什么不同。
两者都会因为没有while(1) 在执行到main()最后的时候,让PC指针返回0x0000
(注:如果mian()最后是一个函数,那么这个函数就不是被调用的,而是使用LJMP跳进去的,直到遇到函数最后的RET指令)

我就说之前怎么会有网友问我为什么不加while(1)也能做流水灯,当时我以为是PC指针越过main()后,执行了后面的未知汇编代码偶尔导致
的单片机重启,现在看来C51本来就是会让它重启的~~

但这只能说明当前的调试环境的情况,而且软件仿真的时候 相同的代码 偶尔也有没跑回0x0000,到处乱飞的情况,不知道是不是仿真的BUG

出0入0汤圆

发表于 2013-12-4 16:10:22 | 显示全部楼层
没完全看懂,再讲详细一些~

出0入0汤圆

发表于 2013-12-4 16:23:35 | 显示全部楼层
以后写程序要避免此种情况!

出0入0汤圆

 楼主| 发表于 2013-12-4 16:42:48 | 显示全部楼层
奮闘ing 发表于 2013-12-4 16:10
没完全看懂,再讲详细一些~

什么不懂捏~科普下CALL和RET指令的作用
在C51中,PC指针代表了单片机当前运行的位置,而SP指针对应的就是栈的指针。
调用函数对应的指令为LCALL(还有SCALL之类的),这个指令会自动将当前PC指针+N的和压入到栈中,N的值就是LCAL之下的一条指令的地址


如主贴中图7所示,图7中Register窗口中PC的值为C:0x02EF,C说明这是CODE区的地址。刚好指向了LCALL MCU_Init(),而下一个指令的
地址为0x02F2,当执行LCALL MCU_Init()指令后,单片机自动将0x02F2这个值压入到栈中,同时SP指针+2,变成了0x2A
再接下来PC指针就被MCU_Init()这个常量赋值了,程序运行到了MCU_Init()这个函数里面。

return语句对应的是ret指令,它的作用是从栈中弹出两个字节的数据到PC指针上,也就是让程序运行到C:0x02F2上,这就是程序返回。

出0入0汤圆

发表于 2013-12-4 16:48:08 | 显示全部楼层
我就说之前怎么会有网友问我为什么不加while(1)也能做流水灯,当时我以为是PC指针越过main()后,执行了后面的未知汇编代码偶尔导致
的单片机重启,现在看来C51本来就是会让它重启的~~


的确看到很多51单片机的教学视频出现这个问题。还有的在操作马达的时候停不下来。
请问LZ,对于上面提到的这种情况,有什么办法可以不加while(1)循环的情况下,而至让程序跑一遍就停下来,以达到自己想要的效果?

出0入264汤圆

发表于 2013-12-4 17:02:46 | 显示全部楼层
yaake 发表于 2013-12-4 16:48
的确看到很多51单片机的教学视频出现这个问题。还有的在操作马达的时候停不下来。
请问LZ,对于上面提到 ...

加个变量就可以了啊

if(condition_true)
{
    //执行代码
  
   conditon_true = false;
}

出0入0汤圆

发表于 2013-12-4 17:09:33 | 显示全部楼层
mcu_lover 发表于 2013-12-4 17:02
加个变量就可以了啊

if(condition_true)

嗯,这样加个判断应该可以。多谢回复。

出0入0汤圆

发表于 2013-12-5 09:11:26 | 显示全部楼层
chencc8 发表于 2013-12-4 15:20
在9.0版本上,定义成int main(),并且没有while(1)的话就报错:MAIN.C(28): warning C290: missing retur ...

您研究得真仔细,当时注意到,但也只是避过就算了

出0入0汤圆

发表于 2013-12-5 09:44:53 | 显示全部楼层
楼主的钻研精神值得学习

出0入0汤圆

发表于 2013-12-5 11:13:16 | 显示全部楼层
还好,main中从来没写国return

出0入0汤圆

发表于 2013-12-6 21:15:21 | 显示全部楼层
为什么要在main中加return……

出0入0汤圆

发表于 2013-12-6 21:53:57 | 显示全部楼层
iquer 发表于 2013-12-6 21:15
为什么要在main中加return……

应该是C99 标准要求 main要有返回值。

出0入0汤圆

发表于 2013-12-8 14:15:53 | 显示全部楼层
哈哈,可以做自动复位了

出0入0汤圆

发表于 2013-12-8 15:07:24 | 显示全部楼层
IAR 在main()中加return会有警告, 但按道理main()永远也不会执行到return 0那个地方去啊,除非有意.

出0入0汤圆

发表于 2013-12-8 15:28:14 | 显示全部楼层
本帖最后由 Alimy 于 2013-12-8 15:30 编辑

KeilC51的确是这样子的,学了一招,以后不会在main里面写return了。。
在执行main的时候,返回地址没有压栈。

出0入0汤圆

发表于 2013-12-8 15:30:20 | 显示全部楼层
佩服佩服,向楼主的钻研精神学习。不过怎么会有人想到在主程序里面使用return呢,这个是为你了返回函数的啊。至于有人说要退出while循环,这个可以使用break啊

出0入0汤圆

发表于 2013-12-8 16:44:14 | 显示全部楼层
typedef 发表于 2013-12-4 11:03
新版本的Keil C51 V9.52 在main 中使用 return x; 时会有警告

同意

出0入0汤圆

 楼主| 发表于 2013-12-8 23:38:38 | 显示全部楼层
llb126yx 发表于 2013-12-8 15:30
佩服佩服,向楼主的钻研精神学习。不过怎么会有人想到在主程序里面使用return呢,这个是为你了返回函数的啊 ...


~我也不清楚为什么这样用,刚来这家公司工作
公司里有些工程师之前貌似没多牢固的基础知识,所以经常能在他们面前摆显一下哈哈,
不过他们会的外设倒是比较时髦,比如WIFI 蓝牙之类的

出0入0汤圆

发表于 2013-12-31 08:18:02 | 显示全部楼层
在程序中想要软件重启的话,在main函数调用一下return就行了 ??

出0入0汤圆

发表于 2013-12-31 08:42:05 | 显示全部楼层
嗯,学习了。之前看单片机原理,有些不明白的地方。

出0入0汤圆

发表于 2013-12-31 08:45:33 | 显示全部楼层
对于单片机的话,不是一般都
//...
while(1)
{
//...
}
return 0;
这样么。。。。

出0入0汤圆

发表于 2013-12-31 09:14:55 | 显示全部楼层
用好了就是个自动重启的功能啊~

出0入0汤圆

 楼主| 发表于 2013-12-31 09:20:17 | 显示全部楼层
圣骑士by 发表于 2013-12-31 08:18
在程序中想要软件重启的话,在main函数调用一下return就行了 ??


只能说 已知C51是这样的,别的单片机就不能这样做了,刚试了下STM32的话是会调用main()函数的,return后会返回调用main()的地方,然后进入到一个带有软件断点(BKPT指令)的死循环中。

出0入0汤圆

发表于 2013-12-31 09:29:41 | 显示全部楼层
学习了,谢楼主

出0入0汤圆

发表于 2013-12-31 18:18:40 | 显示全部楼层
受教了!!!

出0入0汤圆

发表于 2013-12-31 22:22:20 | 显示全部楼层
PC机上的程序main的return是返回操作系统
单片机上没有操作系统,main中的return是没有意义的。
回帖提示: 反政府言论将被立即封锁ID 在按“提交”前,请自问一下:我这样表达会给举报吗,会给自己惹麻烦吗? 另外:尽量不要使用Mark、顶等没有意义的回复。不得大量使用大字体和彩色字。【本论坛不允许直接上传手机拍摄图片,浪费大家下载带宽和论坛服务器空间,请压缩后(图片小于1兆)才上传。压缩方法可以在微信里面发给自己(不要勾选“原图),然后下载,就能得到压缩后的图片。注意:要连续压缩2次才能满足要求!!】。另外,手机版只能上传图片,要上传附件需要切换到电脑版(不需要使用电脑,手机上切换到电脑版就行,页面底部)。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

手机版|Archiver|amobbs.com 阿莫电子技术论坛 ( 粤ICP备2022115958号, 版权所有:东莞阿莫电子贸易商行 创办于2004年 (公安交互式论坛备案:44190002001997 ) )

GMT+8, 2024-8-26 05:14

© Since 2004 www.amobbs.com, 原www.ourdev.cn, 原www.ouravr.com

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