详解C语言中return返回函数局部变量的问题
在计算机中,释放空间并不需要将空间中的内容全部置成0或者1,而是只要设置这一块空间的数据无效即可。比如在下载文件时需要花很长时间,但是删除文件却只要几秒钟,这是因为操作系统只是把文件标识(文件头链接)删掉了,文件原文还保留着,我们没了文件标识就找不到这个文件了。所以删除后的文件,还可以用特殊的办法被找回来。
这也就意味着,当函数结束调用的时候,函数中的局部变量实际上还是在的,只是函数原来的空间还给编译器(释放)了,也就是说函数中的局部变量是可以被编译器修改的。
虽然函数结束后空间还给了编译器,但是我们依然可以通过指针找到对应的局部变量的空间。
return返回栈区局部变量的指针
通过上面的分析,如果我们返回局部变量的指针,是不是可以直接找到这个局部变量呢?
返回了一个局部变量的指针,而恰好局部变量偏偏又在函数结束后销毁,但指针并没有被销毁,而是被返回,那也就是说,指针指向的正是一个被销毁了的对象。
比如下面的代码:
#include <stdio.h> char* returnStr() { char p[] = "hello world!"; return p; } int main() { char* str; str = returnStr(); printf("%s\n", str); return 0; }
可以看到原来p的空间已经被修改了,但是很奇怪,是谁进行的修改呢?
通过调试可以发现,当函数结束后,运行printf打印之前,str指向的空间中的字符串是在的:
但是一运行printf打印操作,str指向的空间中的字符串就会被修改:
这其实很好解释,因为printf本身也是一个函数,函数都是在栈区开辟的,而函数开辟的空间叫做栈帧,函数结束栈帧就还给了编译器:
了解了这些,那是不是只要我们多建立几个函数栈帧,是不是就可以使returnStr的函数栈帧不被覆盖了?答案是肯定的:
#include <stdio.h> char* fun6() { char p[] = "hello world"; return p; } char* fun5() { return fun6(); } char* fun4() { return fun5(); } char* fun3() { return fun4(); } char* fun2() { return fun3(); } char* fun1() { return fun2(); } char* fun() { return fun1(); } int main() { char* str; str = fun(); printf("%s\n", str); return 0; }
不过随着函数栈帧的逐渐增多,原来的fun6函数空间迟早也会被覆盖。
在函数体内定义的局部变量是有临时性的,当局部变量释放后随时都有可能会被修改,所以我们不能通过指针使用已经被释放的局部变量。
return返回栈区局部的临时变量
如果我们不返回指针,而是返回局部变量会怎么样呢?
按理来说局部变量也会被修改。
#include<stdio.h> int test() { int a = 10; return a; } int main() { int b = test(); printf("%d", b); }
通过反汇编可以看到,局部变量a的值10通过寄存器交给了调用的b,所以即使局部变量a空间的10已经被修改,也不会影响b的内容:
return只读数据段和static数据
如果返回只读字符串则不会被覆盖,因为数据不是在栈区,而是在静态区:
同理如果将字符串用static修饰也是如此:
另外,返回堆内的指针也是可以的。