详解C语言中return返回函数局部变量的问题

目录
  • return返回栈区局部变量的指针
  • return返回栈区局部的临时变量
  • return只读数据段和static数据

在计算机中,释放空间并不需要将空间中的内容全部置成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修饰也是如此:

另外,返回堆内的指针也是可以的。

到此这篇关于详解C语言中return返回函数局部变量的问题的文章就介绍到这了,更多相关C语言return返回函数局部变量内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • 详解C语言中return与exit的区别

    详解C语言中return与exit的区别 1,exit用于在程序运行的过程中随时结束程序,exit的参数是返回给OS的.main函数结束时也会隐式地调用exit函数.exit函数运行时首先会执行由atexit()函数登记的函数,然后会做一些自身的清理工作,同时刷新所有输出流.关闭所有打开的流并且关闭通过标准I/O函数tmpfile()创建的临时文件.exit是结束一个进程,它将删除进程使用的内存空间,同时把错误信息返回父进程,而return是返回函数值并退出函数 2,return是语言级别的,它

  • C语言return知识点总结

    return.break和continue 这三个关键字有一个共同点,那就是读能让后面的语句不执行,不同的地方就是挑的距离不一样. return很强大,如果一个函数中有一个return,并且执行了,那么这个函数就完了.return 表示从被调函数返回到主调函数继续执行,返回时可附带一个返回值,由return后面的参数指定. return通常是必要的,因为函数调用的时候计算结果通常是通过返回值带出的. 如果函数执行不需要返回计算结果,也经常需要返回一个状态码来表示函数执行的顺利与否(-1和0就是最

  • c语言中return与exit的区别浅析

    1. exit 用于在程序运行的过程中随时结束程序,exit 的参数是返回给OS的.main函数结束时也会隐式地调用exit函数.exit函数运行时首先会执行由atexit()函数登记的函数,然后会做一些自身的清理工作,同时刷新所有输出流.关闭所有打开的流并且关闭通过标准I/O函数tmpfile()创建的临时文件.exit是结束一个进程,它将删除进程使用的内存空间,同时把错误信息返回父进程,而return是返回函数值并退出函数. 2. return是语言级别的,它表示了调用堆栈的返回:而exit

  • 详解C语言中return返回函数局部变量的问题

    目录 return返回栈区局部变量的指针 return返回栈区局部的临时变量 return只读数据段和static数据 在计算机中,释放空间并不需要将空间中的内容全部置成0或者1,而是只要设置这一块空间的数据无效即可.比如在下载文件时需要花很长时间,但是删除文件却只要几秒钟,这是因为操作系统只是把文件标识(文件头链接)删掉了,文件原文还保留着,我们没了文件标识就找不到这个文件了.所以删除后的文件,还可以用特殊的办法被找回来. 这也就意味着,当函数结束调用的时候,函数中的局部变量实际上还是在的,只

  • 详解C语言中的memset()函数

    C语言memset()函数:将内存的前n个字节设置为特定的值 头文件: #include <string.h> memset() 函数用来将指定内存的前n个字节设置为特定的值,其原型为: void * memset( void * ptr, int value, size_t num ); 参数说明: ptr 为要操作的内存的指针. value 为要设置的值.你既可以向 value 传递 int 类型的值,也可以传递 char 类型的值,int 和 char 可以根据 ASCII 码相互转换.

  • 详解C语言中的fopen()函数和fdopen()函数

    C语言fopen()函数:打开一个文件并返回文件指针 头文件: #include <stdio.h> fopen()是一个常用的函数,用来以指定的方式打开文件,其原型为: FILE * fopen(const char * path, const char * mode); [参数]path为包含了路径的文件名,mode为文件打开方式. mode有以下几种方式: 在POSIX 系统,包含Linux 下都会忽略 b 字符.由fopen()所建立的新文件会具有S_IRUSR|S_IWUSR|S_I

  • 详解C语言中的rename()函数和remove()函数的使用方法

    C语言rename()函数:重命名文件或目录 头文件: #include <stdio.h> 函数rename()用于重命名文件.改变文件路径或更改目录名称,其原型为 int rename(char * oldname, char * newname); [参数]oldname为旧文件名,newname为新文件名. [返回值]修改文件名成功则返回0,否则返回-1. 重命名文件: 如果newname指定的文件存在,则会被删除. 如果newname与oldname不在一个目录下,则相当于移动文件.

  • 详解C语言中的wait()函数和waitpid()函数

    C语言wait()函数:结束(中断)进程函数(常用) 头文件: #include <sys/types.h> #include <sys/wait.h> 定义函数: pid_t wait (int * status); 函数说明:wait()会暂时停止目前进程的执行, 直到有信号来到或子进程结束. 如果在调用wait()时子进程已经结束, 则wait()会立即返回子进程结束状态值. 子进程的结束状态值会由参数status 返回, 而子进程的进程识别码也会一快返回. 如果不在意结束状

  • 详解C语言中的ttyname()函数和isatty()函数的用法

    C语言ttyname()函数:返回一终端机名称 头文件: #include <unistd.h> 定义函数: char * ttyname(int desc); 函数说明:如果参数desc 所代表的文件描述词为一终端机, 则会将此终端机名称由一字符串指针返回, 否则返回NULL. 返回值:如果成功则返回指向终端机名称的字符串指针, 有错误情况发生时则返回NULL. 范例 #include <unistd.h> #include <sys/types.h> #includ

  • 详解C语言中的getgrgid()函数和getgrnam()函数

    C语言getgrgid()函数:从组文件中取得指定gid的数据 头文件: #include <grp.h> #include <sys/types.h> 定义函数: strcut group * getgrgid(gid_t gid); 函数说明:getgrgid()用来依参数gid 指定的组识别码逐一搜索组文件, 找到时便将该组的数据以group 结构返回. 返回值:返回 group 结构数据, 如果返回NULL 则表示已无数据, 或有错误发生. 范例 /* 取得gid=3 的组

  • 详解C语言中sizeof如何在自定义函数中正常工作

    1.在main函数中,sizeof是可以正常工作的,比如: int main() { int n[5]; printf("input: \n"); int i ; for(i = 0; i < 5; i++) { scanf("%d",n + i); } int len = sizeof(n)/sizeof(n[0]); printf("%d\n",len); return 0; } 2.但是在自定义函数中就不可以了,如下: #includ

  • 详解go语言中type关键词的几种使用

    type是go语法里的重要而且常用的关键字,type绝不只是对应于C/C++中的typedef.搞清楚type的使用,就容易理解go语言中的核心概念struct.interface.函数等的使用.以下我用例子代码总结描述,请特别留意代码中的注释. 1.定义结构体 //结构体定义 type person struct { name string //注意后面不能有逗号 age int } func main() { //结构体初始化 p := person{ name: "taozs",

随机推荐