欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

深度理解函数返回局部变量问题

程序员文章站 2022-12-29 08:14:35
在被调用函数里把存储区的地址作为返回值使用的时候就可以让调用函数使用被调用函数的存储区。这个时候被调用函数需要提供一个指针类型的存储区记录作为返回值的地址。 且不可以把非静态局部变量存储区的地址作为返回值来使用。这里主要是由函数里局部变量所存在的存储区的类型所决定的。 C程序的存储空间布局可以分为: ......

 

 

  在被调用函数里把存储区的地址作为返回值使用的时候就可以让调用函数使用被调用函数的存储区。这个时候被调用函数需要提供一个指针类型的存储区记录作为返回值的地址。

  且不可以把非静态局部变量存储区的地址作为返回值来使用。这里主要是由函数里局部变量所存在的存储区的类型所决定的。

  c程序的存储空间布局可以分为:

  深度理解函数返回局部变量问题深度理解函数返回局部变量问题

 

  1、正文段:通常存放cpu执行的机器指令部分。

  2、初始化数据段:通常也叫数据段,用来存放程序中已初始化的全局变量的一块内存区域。数据段属于静态内存分配。

  3、未初始化数据段(bss):通常是指用来存放程序中未初始化的或者初始值为0的全局变量的一块内存区域。bss段属于静态内存分配。

  4、栈:存放函数的参数,返回值,局部变量等。

  5、堆:由程序员自行分配释放并管理。

  所以,存储在静态区域的对象的生存周期是主函数的生存周期,而存储在栈区域的对象生存周期为指针函数开始运行到指针函数结束,当指 针函数结束时存储在栈区域的对象生存周期也就结束,其地址也变成无效地址。栈空间由编译器自动分配和释放,函数结束时其栈空间释放内存。堆区域一般由程序员来控制其生存周期。因此,指针函数返回的指针能够指向静态区域的变量而不能指向自动局部变量。

  当函数使用指针作为返回值时,它可以指向静态区域的地址,可以指向堆内存的地址,也可以指向函数调用者的栈空间,但是它不可以指向一个函数内部栈内存的地址。

      因此,能不能返回局部指针变量,不在于这个指针变量的类型和性质(不在于该指针是不是局部指针变量),而在于该指针指向的对象的类型和性质。如果该指针指向函数内部的栈空间,则程序非法,如果指向静态区域的地址,则合法。

下面咱们来看程序,使用的编译器及linux版本为(gcc version 4.8.2 (ubuntu 4.8.2-19ubuntu1))

  局部变量演示:

 1 int *doit(void){
 2     int a = 7;
 3     int *p = &a;
 4     printf("*p = %d\n", *p);
 5     return p;
 6 }
 7 
 8 int main(void){
 9     int *ptr = doit();
10     printf("*ptr = %d\n", *ptr);
11     return 0;
12 }

  编译之后运行,结果如下:深度理解函数返回局部变量问题

  你可以惊奇的发现,函数已经返回了,局部变量回收,为什么*ptr的值还是7呢?

  原来编译器在函数执行结束后,的确会对局部变量进行销毁,但是需要一定的时间。例如以下代码

  

 int *doit(void){
     int a = 7;
     int *p = &a;
     printf("*p = %d\n", *p);
     return p;
 }
  
 int main(void){
     int *ptr = doit();
     printf("*ptr = %d\n", *ptr);
     sleep(1);
     printf("*ptr = %d\n", *ptr); 
     return 0;
 }

  我在第一条打印出sleep 1秒之后再打印输出,可见*ptr的值已经发生改变:

  深度理解函数返回局部变量问题

  所以局部变量的地址是不能当做返回值来使用的,也包括各种类型的变量。若想使用局部变量的地址作为返回值,需要加static。下面来看一下:

 int *doit(void){
     static int a = 7;
     int *p = &a;
     printf("*p = %d\n", *p);
     return p;
 }
  
 int main(void){
     int *ptr = doit();
     printf("*ptr = %d\n", *ptr);
     sleep(1);
     printf("*ptr = %d\n", *ptr); 
     return 0;
 }

  这样运行结果就是:

  深度理解函数返回局部变量问题  符合预期要求。

  当局部变量为数组的时候,也是不可以的:

int * doit(void){
    char arr[] = "abc";
    printf("%s\n", arr);
    return arr;
}
int main(void){
    char *p = doit();
    printf("p = %s\n", p); 
    sleep(1);
    printf("p = %s\n", p); 
    return 0;
}

  结果为:

  深度理解函数返回局部变量问题

  但是如果字符串是这种形式:

int * doit(void){
    char *p = "abc";//在静态区域存放其副本值
    printf("%s\n", p);
    return p;
}
int main(void){
    char *p = doit();
    printf("p = %s\n", p); 
    sleep(1);
    printf("p = %s\n", p); 
    return 0;
}

  结果为:

  深度理解函数返回局部变量问题

  若用malloc在堆区申请内存,则:

int * doit(void){
    char *p = (char *)malloc(sizeof(int));
    strcpy(p, "abc");
    printf("%s\n", p);
    return p;
}
int main(void){
    char *p = doit();
    printf("p = %s\n", p); 
    sleep(1);
    printf("p = %s\n", p);
    free(p); 
    return 0;
}

  则:深度理解函数返回局部变量问题

  可见要深刻理解这句话:能不能返回局部指针变量,不在于这个指针变量的类型和性质(不在于该指针是不是局部指针变量),而在于该指针指向的对象的类型和性质。