返回局部指针变量

本文深入探讨了函数返回局部变量、引用及指针的风险与注意事项,分析了不同存储区域对象的本质区别,提供了避免常见陷阱的实用建议。

返回值为数值时,返回局部变量可以,而返回值为引用或指针时,则存在风险。

局部变量是存在于栈中的,函数被调用时先在栈中为变量申请空间,调用完成释放变量空间。函数在返回参数的时候是这样的,先把要返回的数放在寄存器eax中,然后回到主函数中取出eax中的数值放在变量里,所以这样是不涉及函数中变量地址的。

如果要返回引用,也就是变量地址,那么它会把这个变量的地址放在eax中,(注意这个地址是位于函数的栈空间里的,出了这个函数,这块内存就会被系统标记为可占用(就是其它程序可以占用)),回到主函数后系统会把这个地址赋值给主函数中的指针变量。此时主函数中的指针变量就指向了一个已经被标记为可占用的内存空间。如果你在不同的时刻输出这个指针所指地址的值会输出不同的结果。

  1. int * fun()  
  2. {  
  3.  int p;  
  4.  p = 100;  
  5.  return &p;  
  6. }  
  7. int * fun2()  
  8. {  
  9.  int p;  
  10.  p = 200;  
  11.  return &p;  
  12. }  
  13. void main()  
  14. {  
  15.  int *p , *q;  
  16.  p = fun();  
  17.  printf("%d\n" , *p);//输出100  
  18.  q = fun2();  
  19.  printf("%d\n" , *p);//输出200  
  20.  return;  
  21. }  
指针函数的返回指针不能指向函数内的自动变量,如果需要返回函数的内部变量的话,就需要将该变量声明为静态变量。为什么函数能够返回 静态变量的地址而不能返回局部自动变量的地址,到底什么样的对象能够返回其地址,而什么样的对象不能够返回其地址?静态变量与局部自动变量的主要区别是什 么?

要想明白这些就需要理解程序的内存布局情况
程序的存储区域分为:代码段、只读数据段、已初始化的读写数据段、未初始化的数据段、堆、栈。
1、代码段、只读数据段、已初始化的读写数据段、未初始化的数据段都属于静态区域。
2、堆内存只在程序运行时出现,一般有程序员分配和释放。
3、栈内存只在程序运行时出现,在函数内部使用的变量,函数参数以及返回值将使用栈空间。

到底存储在静态区域和存储在栈区域的对象在返回指针的函数中有什么本质区别,为什么存储在静态区域的静态变量就能够返回其地址,而存储在栈区域的自动变量不能返回其地址?

主要在于他们的管理机制不同,存储在静态区域的对象的生存周期是主函数的生存周期,而存储在栈区域的对象生存周期为指针函数开始运行到指针函数结束,当指 针函数结束时存储在栈区域的对象生存周期也就结束,其地址也变成无效地址。栈空间由编译器自动分配和释放,函数结束时其栈空间释放内存。堆区域一般由程序 员来控制其生存周期。因此,指针函数返回的指针能够指向静态区域的变量而不能指向自动局部变量。
当函数使用指针作为返回值时,它可以指向静态区域的地址,可以指向堆内存的地址,也可以指向函数调用者的栈空间,但是它不可以指向一个函数内部栈内存的地址。
因此,能不能返回局部指针变量,不在于这个指针变量的类型和性质(不在于该指针是不是局部指针变量),而在于该指针指向的对象的类型和性质。如果该指针指向函数内部的栈空间,则程序非法,如果指向静态区域的地址,则合法。

因此,判断指针函数返回值是否合法,应该首先看看该返回指针变量指向的对象的存储区域,即该指针指向的区域。透过现象看本质,不同区域的对象本质区别在于 其的生存周期的有效性不同,判断返回的指针值是否有效合法,最本质应该看看该指针指向的对象的生存周期在函数结束后是否有效。如果该对象的生存周期长于指 针函数的生存周期,则该指针返回值合法,否则,该指针的值为非法地址。即使该指针指向堆区域的地址但在指针函数结束时,堆已释放,则该函数的返回地址仍为非法。

如果函数的参数是一个指针,不要指望用该指针去申请动态内存。Test函数的语句GetMemory(str, 200)并没有使str获得期望的内存,str依旧是NULL,为什么?

  1. void GetMemory(char *p, int num)  
  2. {  
  3.     p = (char *)malloc(sizeof(char) * num);  
  4. }  
  5. void Test(void)  
  6. {  
  7.     char *str = NULL;  
  8.     GetMemory(str, 100);    // str 仍然为 NULL   
  9.     strcpy(str, "hello");   // 运行错误  
  10. }  
问题出在函数GetMemory 中。编译器总是要为函数的每个参数制作临时副本,指针参数p的副本是 _p,编译器使 _p = p。如果函数体内的程序修改了_p的内容,就导致参数p的内容作相应的修改。这就是指针可以用作输出参数的原因。在本例中,_p申请了新的内存,只是把 _p所指的内存地址改变了,但是p丝毫未变。所以函数GetMemory并不能输出任何东西。事实上,每执行一次GetMemory就会泄露一块内存,因 为没有用free释放内存。
如果非得要用指针参数去申请内存,那么应该改用“指向指针的指针”,如下:

  1. void GetMemory2(char **p, int num)  
  2. {  
  3.     *p = (char *)malloc(sizeof(char) * num);  
  4. }  
  5. void Test2(void)  
  6. {  
  7.     char *str = NULL;  
  8.     GetMemory2(&str, 100);  // 注意参数是 &str,而不是str  
  9.     strcpy(str, "hello");    
  10.     cout<< str << endl;  
  11.     free(str);   
  12. }  
由于“指向指针的指针”这个概念不容易理解,我们可以用函数返回值来传递动态内存。这种方法更加简单:

  1. char *GetMemory3(int num)  
  2. {  
  3.     char *p = (char *)malloc(sizeof(char) * num);  
  4.     return p;  
  5. }  
  6. void Test3(void)  
  7. {  
  8.     char *str = NULL;  
  9.     str = GetMemory3(100);   
  10.     strcpy(str, "hello");  
  11.     cout<< str << endl;  
  12.     free(str);   
  13. }  
用函数返回值来传递动态内存这种方法虽然好用,但是常常有人把return语句用错了。这里强调不要用return语句返回指向“栈内存”的指针,因为该内存在函数结束时自动消亡:

  1. char *GetString(void)  
  2. {  
  3.     char p[] = "hello world";  
  4.     return p;   // 编译器将提出警告  
  5. }  
  6. void Test4(void)  
  7. {  
  8. char *str = NULL;  
  9. str = GetString();  // str 的内容是垃圾  
  10. cout<< str << endl;  
  11. }  
用调试器逐步跟踪Test4,发现执行str = GetString语句后str不再是NULL指针,但是str的内容不是“hello world”而是垃圾。
如果把程序写成下面这样:

  1. char *GetString2(void)  
  2. {  
  3.     char *p = "hello world";  
  4.     return p;  
  5. }  
  6. void Test5(void)  
  7. {  
  8.     char *str = NULL;  
  9.     str = GetString2();  
  10.     cout<< str << endl;  
  11. }  
函数Test5运行虽然不会出错,但是函数GetString2的设计概念却是错误的。因为GetString2内的“hello world”是常量字符串,位于静态存储区,它在程序生命期内恒定不变。无论什么时候调用GetString2,它返回的始终是同一个“只读”的内存块。

原文出自http://blog.chinaunix.net/uid-27411029-id-3497902.html

  1. 原文自http://blog.chinaunix.net/uid-15014334-id-3533931.html  
  1. 一般的来说,函数是可以返回局部变量的。 局部变量的作用域只在函数内部,在函数返回后,局部变量的内存已经释放了。因此,如果函数返回的是局部变量的值,不涉及地址,程序不会出错。但是如果返回的是局部变量的地址(指针)的话,程序运行后会出错。因为函数只是把指针复制后返回了,但是指针指向的内容已经被释放了,这样指针指向的内容就是不可预料的内容,调用就会出错。准确的来说,函数不能通过返回指向栈内存的指针(注意这里指的是栈,返回指向堆内存的指针是可以的)。  
  2. 1. 返回局部变量的值  
  3.   
  4. 可以有两种情况:返回局部自动变量和局部静态变量,比如,  
  5.   
  6. int func()  
  7. {  
  8.     int temp = 0;   // 返回局部自动变量的值  
  9.     return temp;  
  10. }  
  11.   
  12. 局部变量temp存储在栈中,函数返回时会自动复制一份temp的copy给调用者,没有问题。  
  13.   
  14. int func()  
  15. {  
  16.     static int a = 1;   // 返回局部静态变量的值  
  17.     return a;  
  18. }  
  19. 局部变量a存储在静态(全局)存储区中,从初始化后一直有效直到程序结束,仅分配一次内存,并且函数返回后,变量不会销毁,没有问题。  
  20.   
  21. vector<int> func()  
  22. {  
  23.     vector<int> v;  
  24.     v.push_back(0);  
  25.     return v;  
  26. }  
  27.   
  28. 返回的是v的值拷贝,没有问题。  
  29.   
  30. Person func()  
  31. {  
  32.     Person p1;  
  33.     p1.name = "test";  
  34.     return p1;  
  35. }  
  36.   
  37. ?  
  38. 返回的也是值拷贝,会调用Person类的拷贝构造函数,没有问题。  
  39.   
  40. 2. 返回局部变量的指针  
  41.   
  42. int* func()  
  43. {  
  44.     int temp = 0;   // 返回局部变量的地址  
  45.     return &temp;  
  46. }  
  47.   
  48. ?  
  49. 前面讨论过,局部变量temp存储在栈中,函数返回时将已销毁变量的地址返回给调用者,结果将是不可预知的。  
  50.   
  51. int* func()  
  52. {  
  53.     static int temp = 1;  
  54.     return &temp;  
  55. }  
  56.   
  57. ?  
  58. 局部变量temp存储在静态存储区,返回指向静态存储区变量的指针是可行的。  
  59.   
  60. char* func()  
  61. {  
  62.     char *p = "test";  
  63.     return p;   // 返回指向常量字符串的指针  
  64. }  
  65.   
  66. ?  
  67. 对于字符串的特殊情况,由于字符串test存储在常量存储区(不是静态存储区),因此函数返回一个指向常量的字符串指针是可行的。  
  68.   
  69. char* func()  
  70. {  
  71.     char str[] = "test";  
  72.     return str; // 返回局部字符串的指针  
  73. }  
  74.   
  75. ?  
  76. 这种情况下,str被初始化为字符串局部变量,因此函数返回一个已销毁的局部变量是不可行的。解决办法就是将字符串str声明为static。  
  77.   
  78. char* func()  
  79. {  
  80.     char *str = (char *)malloc(sizeof(char) * BUFFER_SIZE);  
  81.     strcpy(str, "test");  
  82.     return str;  
  83. }  
  84.   
  85. ?  
  86. 这种情况下,函数返回一个指向堆内存的指针,由于堆存储区由程序员手动管理,因此这种做法是可行的,但是要防止出现内存泄露,函数调用完后需要手动释放内存。这里的sizeof作用于指针返回的是指针类型的长度1byte,而如果作用于数组返回的则是数组的长度。  
  87.   
  88. char *temp = NULL;  
  89. temp = func();  
  90. // some operation...  
  91. free(temp);  
  92.   
  93. ?  
  94. 3. 返回局部变量的引用  
  95.   
  96. int& func()  
  97. {  
  98.     int temp = 0;   // 返回局部变量的引用  
  99.     return temp;  
  100. }  
  101.   
  102. 由引用的概念可知,函数返回的是temp本身,而temp在函数返回后已销毁,结果将是不可预知的。  
  103.   
  104. 补充:静态全局变量和全局变量的区别  
  105.   
  106. 静态全局变量只在当前文件中可用,全局变量在其他文件中也可用,需要用extern声明。  
  107.   
  108. 全局变量和静态变量如果没有手动初始化,则默认由编译器初始化为0。  
  109.   
  110. 1:  
  111. [cpp] view plaincopy  
  112. #include <stdio.h>     
  113. char *returnStr()     
  114. {     
  115.     char *p="hello world!";     
  116.     return p;     
  117. }     
  118. int main()     
  119. {     
  120.     char *str;     
  121.     str=returnStr();     
  122.     printf("%s\n", str);     
  123.     return 0;     
  124. }    
  125. 这个没有任何问题,因为"hello world!"是一个字符串常量,存放在只读数据段,把该字符串常量存放的只读数据段的首地址赋值给了指针,所以returnStr函数退出时,该该字符串常量所在内存不会被回收,故能够通过指针顺利无误的访问。  
  126. 2:  
  127. [html] view plaincopy  
  128. #include <stdio.h>     
  129. char *returnStr()     
  130. {     
  131.     char p[]="hello world!";     
  132.     return p;     
  133. }     
  134. int main()     
  135. {     
  136.     char *str;     
  137.     str=returnStr();     
  138.     printf("%s\n", str);     
  139.     return 0;     
  140. }     
  141. "hello world!"是局部变量存放在栈中。当returnStr函数退出时,栈要清空,局部变量的内存也被清空了,所以这时的函数返回的是一个已被释放的内存地址,所以有可能打印出来的是乱码。   
  142. 3:  
  143. [html] view plaincopy  
  144. int func()    
  145. {    
  146.       int a;    
  147.       ....    
  148.       return a;    //允许    
  149. }                       
  150.     
  151. int * func()    
  152. {    
  153.       int a;    
  154.       ....    
  155.       return &a;    //无意义,不应该这样做    
  156. }     
  157. 局部变量也分局部自动变量和局部静态变量,由于a返回的是值,因此返回一个局部变量是可以的,无论自动还是静态,  
  158. 因为这时候返回的是这个局部变量的值,但不应该返回指向局部自动变量的指针,因为函数调用结束后该局部自动变量  
  159. 被抛弃,这个指针指向一个不再存在的对象,是无意义的。但可以返回指向局部静态变量的指针,因为静态变量的生存  
  160. 期从定义起到程序结束。  
  161.   
  162. 4:如果函数的返回值非要是一个局部变量的地址,那么该局部变量一定要申明为static类型。如下:  
  163. [html] view plaincopy  
  164. #include <stdio.h>     
  165. char *returnStr()     
  166. {     
  167.     static char p[]="hello world!";     
  168.     return p;     
  169. }     
  170. int main()     
  171. {     
  172.     char *str;     
  173.      str=returnStr();     
  174.     printf("%s\n", str);     
  175.     
  176.     return 0;     
  177. }     
  178. 5: 数组是不能作为函数的返回值的,原因是编译器把数组名认为是局部变量(数组)的地址。返回一个数组一般用返回指向这个数组的指针代替,而且这个指针不能指向一个自动数组,因为函数结束后自动数组被抛弃,但可以返回一个指向静态局部数组的指针,因为静态存储期是从对象定义到程序结束的。如下:  
  179. [html] view plaincopy  
  180. int* func( void )    
  181. {    
  182.     static int a[10];    
  183.     ........    
  184.     return a;    
  185. }     
  186. 6:返回指向堆内存的指针是可以的  
  187. [html] view plaincop  
  188. char *GetMemory3(int num)    
  189. {    
  190. char *p = (char *)malloc(sizeof(char) * num);    
  191. return p;    
  192. }    
  193. void Test3(void)    
  194. {    
  195. char *str = NULL;    
  196. str = GetMemory3(100);    
  197. strcpy(str, "hello");    
  198. cout<< str << endl;    
  199. free(str);    
  200. }    
  201. 程序在运行的时候用 malloc 申请任意多少的内存,程序员自己负责在何时用 free释放内存。动态内存的生存期一直到程序员自己释放。  

  1. 可以函数的参数表中传入一个指针变量,然后局部指针指向这个参数指针,则当函数返回时,由于参数指针所指的内存空间的寿命是主函数的寿命,函数返回时不会被释放。  
  1. int *fun(int *p)  
  2. {  
  3.     int *a;  
  4.     *p=4;  
  5.     a=p;  
  6.     return a;  
  7. }  
  8. int main()  
  9. {  
  10.     int a=2;  
  11.     int *p=&a;  
  12.     int *r=fun(p);  
  13.     cout<<*r;  
  14.     return 0;  
  15. }  
<think> 我们正在讨论局部指针变量的释放问题。用户的问题集中在“局部指针变量是否会自动释放”。根据之前的引用和C语言的内存管理规则,我们需要明确几个关键点: 1. **局部变量的生命周期**:在C语言中,局部变量(包括指针变量)存储在栈上,其生命周期仅限于定义它的函数内部。当函数执行结束时,这些局部变量所占用的内存会被自动释放(即栈帧被弹出)。这意味着,从内存管理的角度看,局部变量在函数返回后就不再有效。 2. **指针变量与指向的内存**:需要区分指针变量本身和指针所指向的内存。指针变量本身是局部变量,会被自动释放。但是,如果指针指向的内存是动态分配的(例如使用`malloc`),那么这块内存在函数返回后仍然存在,直到显式调用`free`。如果指针指向的是另一个局部变量(例如另一个函数的局部变量),那么当那个函数的栈帧被释放后,该内存也会变得无效。 3. **返回局部指针变量的危险**:如果函数返回一个指向其局部变量指针,那么当函数返回后,这个指针就变成了悬垂指针(dangling pointer),因为它指向的内存已经被释放。任何通过这个指针访问内存的行为都是未定义的,通常可能导致程序崩溃或数据错误。 参考引用: - 引用[1]中的代码展示了返回局部变量`c`的地址,这会导致`main`函数中的`p`指向一个已经被释放的栈内存。 - 引用[2]提供了一种安全的方法:通过传入一个指针参数(该指针指向的内存生命周期更长,如主函数中的变量),然后返回这个指针。这样返回指针所指向的内存是有效的。 - 引用[3]则提出了一个疑问:局部变量的释放是否与数据类型有关?实际上,释放只与存储位置(栈、堆等)和生命周期规则有关,与数据类型无关。 因此,回答用户的问题: - **局部指针变量本身会自动释放**:因为它是局部变量,存储在栈上,函数退出时栈帧被销毁,该指针变量占用的内存被回收。 - **但指针指向的内存是否释放则取决于该内存的来源**: - 如果指向的是动态分配的内存(堆内存),则不会自动释放,需要手动释放。 - 如果指向的是其他局部变量(栈内存),那么当这些变量的生命周期结束时(例如它们的函数退出),这块内存也会被释放,从而形成悬垂指针。 为了更清晰地说明,我们给出一个例子: ### 示例1:返回指向局部变量指针(危险) ```c int* dangerous_func() { int local = 10; // 局部变量,存储在栈上 int *ptr = &local; // ptr是局部指针变量,指向local return ptr; // 返回ptr:危险!因为local的内存将在函数返回后被释放 } int main() { int *p = dangerous_func(); printf("%d\n", *p); // 未定义行为:访问已释放的栈内存 return 0; } ``` 在此例中,`ptr`本身(指针变量)在`dangerous_func`返回时被释放,同时它指向的`local`也被释放。因此,`p`在主函数中成为一个悬垂指针。 ### 示例2:返回指向动态分配内存的指针(安全,但需手动释放) ```c int* safe_func() { int *ptr = (int*)malloc(sizeof(int)); // 动态分配内存,存储在堆上 *ptr = 10; return ptr; // 返回指针:安全,因为堆内存不会自动释放 } int main() { int *p = safe_func(); printf("%d\n", *p); // 输出10 free(p); // 需要手动释放,否则内存泄漏 return 0; } ``` 在此例中,`ptr`本身(指针变量)是局部的,会被自动释放,但它指向的堆内存不会自动释放。因此,返回指针`p`在`main`中仍然有效,但使用后需要手动释放。 ### 结论 - **局部指针变量本身会被自动释放**(因为它是栈上的局部变量)。 - **它指向的内存是否释放**取决于该内存的分配方式: - 指向栈内存(如局部变量):函数返回后内存无效。 - 指向堆内存(如`malloc`分配):内存有效直到显式释放。 - 指向静态存储区或全局变量:内存始终有效。 因此,避免返回指向局部变量指针。如果需要在函数外部使用指针指向的数据,应该: 1. 使用动态分配(堆内存),并记得之后释放。 2. 使用静态局部变量(但需注意线程安全和重入问题)。 3. 将指针作为参数传入,并修改传入指针所指向的内容(如引用[2]所示)。 ### 相关问题 基于上述讨论,生成以下相关问题: 1. 如何避免返回局部变量指针导致的悬垂指针问题? 2. 动态分配的内存如果不释放会导致什么后果? 3. 静态局部变量和全局变量返回指针时有何优缺点? </think> ### 局部指针变量是否会自动释放? 在C语言中,**局部指针变量本身会自动释放,但其指向的内存是否释放取决于内存的来源**。以下是详细分析: #### 1. **指针变量本身的释放(自动释放)** 局部指针变量作为栈内存对象,在函数退出时会被自动销毁: ```c void example() { int* ptr; // 局部指针变量 // ...操作... } // 函数结束时,ptr的栈内存自动释放 ``` - ✅ **指针变量内存**:存储在栈中,函数返回时自动释放[^1][^3] - ✅ **释放机制**:由编译器自动管理,无需手动干预 #### 2. **指针指向内存的释放(分情况)** | 内存来源 | 是否自动释放 | 示例 | |---------------------------|--------------|----------------------------------------------------------------------| | **指向栈内存** | ❌ 不会 | 返回局部变量地址导致悬垂指针[^1]:`return &local_var;` | | **指向堆内存(malloc)** | ❌ 不会 | `int* p = malloc(sizeof(int));` 需手动`free()` | | **指向静态/全局内存** | ❌ 不会 | `static int x; int* p = &x;` 生命周期持续到程序结束 | | **指向传入参数的内存** | ❌ 不会 | 通过参数指针访问外部有效内存[^2]:`int* func(int* ext_ptr) { return ext_ptr; }` | #### 3. 关键案例解析 **危险操作(返回局部变量地址):** ```c int* get_sum(int a, int b) { int result = a + b; // 栈内存变量 return &result; // ❌ 返回后result内存失效 } // result自动释放,返回指针变"悬垂指针" ``` 调用后访问`*get_sum(3,4)`会导致未定义行为(可能崩溃或数据错误)[^1] **安全操作(返回参数指针):** ```c int* safe_get(int* res_ptr, int a, int b) { *res_ptr = a + b; return res_ptr; // ✅ 指向调用者控制的内存 } int main() { int result; // main函数的栈内存 int* p = safe_get(&result, 3, 4); // 有效访问[^2] } ``` #### 4. 数据类型无关性 局部变量的释放**与数据类型无关**,无论是`char*`、`int*`还是自定义结构体指针,都遵循相同的栈内存管理规则[^3]。例如: ```c void data_type_demo() { char* c_ptr; int* i_ptr; struct MyStruct* s_ptr; } // 所有指针变量同时释放,但指向的内存需单独处理 ``` ### 总结 1. **指针变量本身** ✅ 自动释放(栈内存管理) 2. **指针指向的内存** - 栈内存:自动释放 ✅ - 堆内存:需手动释放 ❌ (`free()`) - 静态/全局内存:程序结束时释放 ❌ 3. **最佳实践** - 绝不返回局部变量地址 - 跨函数传递指针时,确保目标内存生命周期足够长[^2] - 动态内存必须配对`malloc`/`free`
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值