关于返回局部变量(栈内存)的指针或引用
年轻的人们,你们要相信科学手段,对于科学的奥秘,我们几乎一无所知。不要气馁,你们要生活在肃然宁静的实验室和图书馆中。这样到你生命的尽头,你将可以说,我已经尽力而为了。--- 巴斯德(Louis Paster,1822-1895,法国人,著名化学家)
这是一个网友提的一个问题,源代码如下:(我在VC6环境下作了一个分析)
![](https://i-blog.csdnimg.cn/blog_migrate/6810355c2f78c12e91b7997a8e8c583a.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/6810355c2f78c12e91b7997a8e8c583a.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/6810355c2f78c12e91b7997a8e8c583a.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/6810355c2f78c12e91b7997a8e8c583a.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/6810355c2f78c12e91b7997a8e8c583a.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/6810355c2f78c12e91b7997a8e8c583a.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/6810355c2f78c12e91b7997a8e8c583a.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/6810355c2f78c12e91b7997a8e8c583a.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/6810355c2f78c12e91b7997a8e8c583a.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/a41954a27d6ad96fa2c2cf816e677448.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/1327ab569c1ae82736693a50b8e33378.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/6a9c071a08f1dae2d3e1c512000eef41.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/6a9c071a08f1dae2d3e1c512000eef41.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/0196c3df5ea9e936f21e9932cca91014.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/6810355c2f78c12e91b7997a8e8c583a.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/6810355c2f78c12e91b7997a8e8c583a.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/a41954a27d6ad96fa2c2cf816e677448.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/1327ab569c1ae82736693a50b8e33378.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/6a9c071a08f1dae2d3e1c512000eef41.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/6a9c071a08f1dae2d3e1c512000eef41.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/6a9c071a08f1dae2d3e1c512000eef41.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/0196c3df5ea9e936f21e9932cca91014.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/6810355c2f78c12e91b7997a8e8c583a.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/6810355c2f78c12e91b7997a8e8c583a.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/a41954a27d6ad96fa2c2cf816e677448.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/1327ab569c1ae82736693a50b8e33378.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/6a9c071a08f1dae2d3e1c512000eef41.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/6a9c071a08f1dae2d3e1c512000eef41.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/6a9c071a08f1dae2d3e1c512000eef41.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/6a9c071a08f1dae2d3e1c512000eef41.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/6a9c071a08f1dae2d3e1c512000eef41.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/6a9c071a08f1dae2d3e1c512000eef41.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/6a9c071a08f1dae2d3e1c512000eef41.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/0196c3df5ea9e936f21e9932cca91014.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/6810355c2f78c12e91b7997a8e8c583a.gif)
(1) 结果:
a1 = 7
b1 = 4198610
(注:4198610对应的十六进制为0x4010D2)
(2) 实验过程:
(以下说的Watch窗口,Disassembly窗口,Memory窗口等都是指View ->Debug Windows下的几个窗口,^_^,很有用的.)
我在两个cout语句前均设置了断点,在watch窗口Name下设置*b1,发现执行到第一个cout语句时,*b1仍为9;但是执行完第一个cout语句时,*b1就变为4198610.
另外,不通过在Watch窗口中查看*b1的Value,同样可以看到结果,方法是通过Disassembly窗口得到的汇编代码,
可以计算出b变量所在的有效地址:
16: return &b;
004015E8 lea eax,[ebp+8]
通过查看寄存器ebp中的值,再加上8即为b变量的有效地址,同时作为返回值赋给b1.这里为0x12FF28.
但是可以通过这个有效地址在Memory窗口观察*b1对应的值.
当执行到第二个cout语句时,0x12FF28的值变为D2 10 40,即0x4010D2,对应十进制4198610.
(3) 原因分析:
这是因为在退出某函数后,函数栈帧释放以后,其空间就可以被程序本身的其它部分或者其它程序占用.
因而其结果是不确定的.(例如在GCC下运行这个程序就会得到不一样的结果.)这一点在<< Effective C++ 2nd >> Item31上也讲到了.
另外一个小小的技巧,就是Memory窗口输入的虽然是变量的有效地址,但是结果还是能找到对应的变量的值.
(4) 作一点重要的补充:
#include < cstdio >
char * get_str()
{
char * str = { " abcd " }; // 可以
// char str[] = {"abcd"}; // 错误,结果不确定
return str;
}
int main()
{
char * p = get_str();
printf( " %s " , p);
return 0 ;
}
char *str = {"abcd"};//可以,是因为"abcd"是一个字符串常量,它并不在一个栈空间上,而是在静态存储区上。
字符串的生命周期是:程序开始时分配,程序结束时释放。
(当然,str是一个指针变量,在栈空间上,因此函数返回时会释放,但是所指为字符串常量,字符串常量所占空间在函数返回时不会释放)
而char str[] = {"abcd"};//错误,结果不确定,虽然从编译器的实现来看(见如下代码(VC6下)的结果),"abcd"仍然是当成一个字符串常量,但是程序又把它复制了一次,放在了栈空间上,返回的时候str所指的是栈上的这一块字符串。问题的本质仍然是由于返回了指向栈空间的指针。
// 00401038 mov dword ptr [ebp-4],offset string "abcd" (0042201c)
// 6: return str;
// 0040103F mov eax,dword ptr [ebp-4]
// 5: char str[] = {"abcd"};
// 00401038 mov eax,[string "abcd" (0042201c)]
// 0040103D mov dword ptr [ebp-8],eax
// 00401040 mov cl,byte ptr [string "abcd"+4 (00422020)]
// 00401046 mov byte ptr [ebp-4],cl
// 6: return str;
// 00401049 lea eax,[ebp-8]