先看下面一段程序:
初步看,Test这个函数很不和谐,p是个局部变量,而返回值也是它,函数返回时它的空间就不复存在了。可当你写代码写得晕头转向的时候,转念一想,p也是个常量指针,"hello world!"这个串并不存储在栈区,而存储于常量区呀,即使栈的内容消失了,返回指向常量区的一个指针也不会有什么问题。
那就试试吧。
在15行 printf(str)处设置断点,DEBUG,运行到断点处的时候,发现str指向确实是"hello world!"串,而且n的值也是12,心中窃喜!继续单步执行,打印出来的结果却不是原来str中的串,变成了"hell??"只有前面4个字节还在,后面的内容全没了,而且str中的内容也是这个"hell??"了,难道在printf中有什么秘密?
于是再手工打造一个简单字符串拷贝函数,程序改写成下:
继续调试,发现执行完copystr后,str中的内容变成了“烫烫烫”,看来不是printf引起的问题,肯定是在调用copystr的时候,修改了str所指的内容。
究竟是什么修改了str,难道常量区的内容被莫名修改??
回到堆栈的问题上来,我们可以在调试中看各种变量的地址及内存中的信息。
继续看第一段代码,在Test函数的第一行设置断点,运行到此的时候查看p的值,发现是0x0012fedc,打开内存查看器,找到0x0012fedc处,发现附近值全为cc,单步运行一次,此处的值变为了"hello world!"。可见p在被初始化的时候,是从某个地方把"hello world!"搬移到p所在的地址的,并不是p一开始就指向了某个内容区域(前面说的常量区域)。根据这个现象可以推测,p就是放在栈中变量,而"hello world"是存在于一个数据区域(堆区?可堆区是在运行时分配空间的?不大明白这些之间的概念)。
为了查找真相,翻下对应的汇编代码:
根据第一行汇编 , "hello world!"串应该存在0x0041573c处,从内存查看器中查看此处内存,确实存在这个串。这段汇编的作用就是通过几组寄存器改变指向"hello world!"串不同的位置,将其搬运到 [ebp-18h]处的栈中(ebp[0x0012fe04]一般用于栈基地址)。
通过上面的分析可以知道,p是存在于栈区,指向的内容也在栈中,Test函数返回的是Test函数栈的一个地址,而函数退出后,栈基址ebp发生变化,变为0x0012ff68(Test中为 0x0012fe04),即为进入Test前的栈基址,此时Test函数栈的内存区域已不受保护,随时可能被系统改写内容,这就解释了调用printf或者copystr时,str的内容发生了变化。
既然知道了原理,那就做些改进。一个直接的想法就是将Test函数中的p直接指向常量区域,将其改为static char p[] = "hello world!"即可,产生的汇编代码就一句:mov eax,offset p (417038h) 。