main函数是必须的吗
研究实验4
研究过程:
问题引出:C语言编程非得用主函数main吗,不用是否可以?
对此问题进行研究,用tc.exe书写代码如下:
图1 没有main函数的c程序
对其进行编译,链接发现,编译阶段可以完成,但是链接阶段无法完成。即无法通过这种方式生成.exe文件。并显示错误信息:
图2 f()程序提示错误信息
错误信息提示没有定义在c0s中没有定义main。
用link.exe对其生成f.exe文件,查看其汇编代码如下:
图3 f.c对应汇编代码
从图中可以看出来,f()的偏移地址是0,这个与main函数不同,使用g命令
G 1d发现无法返回,且DOS卡死!f.exe的代码共1Dh(29)个字节。
图4 m.c汇编代码
可以发现,m.exe程序比f.exe程序多一条ret语句,即m.exe有30个字节,且程序能够正常返回,且程序执行完076a:0217处的指令就返回。
由此可见,main函数与f.exe首先在偏移地址上就不同f.exe偏移地址是0000h,main函数偏移地址是01fah,其次main函数比f.exe多一条ret指令,使得程序能够正常返回
问,添加了这样一条语句,为什么无法执行,且还会导致dos环境崩溃???!!!!!
问题2:main函数结尾的两个ret分别具有什么含义?
图5 探究ret追踪调用main 函数的地址
易知,call指令占用三个字节,当单步执行ret的时候,我们发现,ip变成了011d,基于之前学习对于cs:ip的理解,我们断定,“call main”的偏移地址为011a,结果如下图:
图6 调用main函数指令地址
由图可以发现,偏移地址确实是地址确实是011a。
下面对c0s进行研究:
将c0s.obj生成.exe文件,并用debug加载,查看汇编代码:如下
图7-8 c0s.exe开头对应汇编代码
图9-10 m.exe开头对应汇编代码
通过观察发现,开头处的汇编语句,出了第一句对DX的赋值不同以外,其余都是相同的。
图11 m.exe调用main处汇编代码
图12 c0s.exe相同偏移处代码
从图7-图12我们可以发现,c0s.exe和我们任意的一个m.exe程序很多内容都是相同的。通过图中第一处红线我们还可以发现,main是c0s.exe的一个实参,即调用main函数的是c0s.exe这个程序!!还可以发现,其余存在很多相同的内容,应该是资源配置的初始化,总之,在这里,我们得到的最重要一条结论就是:main函数被c0s.0bj文件调用!!!随着以后编译环境的改变,或者C语言学习的深入,我们仍然要注意这个本质性问题,虽然对于我们学习语言本身没多大帮助,但对于理解其工作机制却很有帮助!
通过上述,我们知道,main函数只不过是一个入口参数而已,并不具有特殊性。因此,我们想办法看能不能不用main函数编程,首先我们重写一个c0s.asm程序,对其编译,替换原c0s.obj。重新编译连接f.c,发现可以成功对f.c编译连接!!我们直接运行f.exe ,发现程序可以正常运行并返回!如下图
图13 得以正常运行的f.exe
我们通过debug加载f.exe,u命令查看开始处代码:如下
图14-15 debug加载f.exe
通过代码,我们发现,程序将我们自己写的c0s代码也加载进去了,再次印证了以下观点:1.c0s文件和用户obj文件一同连接,生成相应的exe文件;2.所谓的main,f不过是一个入口参数而已,本身并无特地位!
下面重新研究向一块安全的内存中写入”a”到”h”,汇编代码如下:
图16-19 安全空间写入”a”到”h”
我们可以发现一个很奇怪的现象,程序中经常刷新es寄存器的值!!!
图20-21 内存结果显示
可以看出ds:0开始处存放额是a-h,在这里我们需要注意的是,es只管下一条语句的段地址!之后默认的是ds!务必明确这一点!
未解决的问题:
Main函数的结尾处为什么有两个ret?
对于向安全的空间写数据,为什么会经常刷新es寄存器的值?
总结感悟:
对于这次学习我认为学习到最重要的一点是对MIAN函数的认识更加深刻了,即MAIN函数本身并不具有一定的特殊性!仅此而已。期间对于追踪调用MAIN函数指令地址进行了思考,并最终通过实践得到了验证。此次研究我认为对于编程技巧没有太大的帮助,但是对于理解本质却很有帮助,另外,对于最后一个向安全空间写数,我认为还任由许多问题应该探讨!