最近在溫習指針的部分時發現了一個有趣的問題,先看以下程序:
//1.c #include<stdio.h> int* fun() { int t = 567; return &t; } int main() { int *p; p = fun(); printf("%d",*p); }
當我把1.c運行后,發現輸出結果是:567。此時編譯器給出警告信息:返回值是局部變量的地址。
首先,我們知道操作系統給函數分配的內存空間都是在棧中,當函數調用結束后,操作系統就會回收其內存空間。當然,這個過程包括回收函數內部的局部變量(局部變量也是存儲在棧空間中),而此處的"回收"是指銷去變量名/函數名,並把其內存空間標記為可用。即操作系統可對該部分內存空間重新利用。但既然變量已經被回收了,為什么還能輸出其值?
對於此問題,我是這樣認為的:
1.當變量的生命周期結束后,雖然變量該變量已不存在,但其之前分配的內存空間還在,也就是其地址還是之前變量的地址,而如果該部分內存空間在main函數結束之前都一直未被操作系統重新利用的話,它的數據沒有被改寫,那么此時輸出*p的值還是之前變量的值。
2.雖然我輸出的*p是567,但是不能保證每次輸出的*p都是567。因為編譯器和操作系統都無法保證這部分內存空間在main函數結束前一直都不會被利用,所以這種輸出是不穩定的。實際上,此時的指針p相當於一個野指針,此時雖然也可以對這部分內存空間進行讀寫操作(可看作對野指針的操作),但是相當危險的。
3.由此可知,一定不要把局部變量的地址作為函數的返回值。
接下來再看另一個程序:
//2.c #include<stdio.h> #include<stdlib.h> int* fun() { int *t = (int*)malloc(sizeof(int)); ; *t = 567; return t; } int main() { int *p; p = fun(); printf("%d",*p); }
當我運行2.c后,發現其輸出結果:567。並且編譯器沒有給出任意的警告信息。
此時t作為一個局部變量,並且函數的返回值為t的地址,為什么編譯器沒給出警告?
在我看來,2.c中的t與1.c中t雖然同為局部變量,兩者在內存中的分布是完全不同的。1.c中的t是存儲在棧空間中的,而2.c中t是存儲在堆空間中的(堆與棧的區別:http://www.cnblogs.com/wangkundentisy/articles/6003482.html)。而當局部變量生命周期結束后,編譯器會先消除該局部變量名,然后對於棧中的空間,編譯器會釋放它;而對於堆中的空間,編譯器並不理會。所以,在2.c中,雖然fun函數以及指針t被銷毀,但t指向的內存空間依然完好無損,並且由於編譯器並未釋放它,操作系統自然不會去利用這部分內存空間。所以,最后輸出*p時,始終會輸出567。但是,這樣會造成內存泄漏,並產生垃圾!所以說malloc之后,一定要有一個free與之相對應!故在2.c中,printf函數后應加上free(p)。
綜上,在寫程序時,一定不要把局部變量的地址作為函數的返回值!一定盡量避免返回在函數內部使用的分配函數(malloc或new)分配的內存空間,以及malloc和free一定要成對的出現!