深度理解函數返回局部變量問題


 

 

  在被調用函數里把存儲區的地址作為返回值使用的時候就可以讓調用函數使用被調用函數的存儲區。這個時候被調用函數需要提供一個指針類型的存儲區記錄作為返回值的地址。

  且不可以把非靜態局部變量存儲區的地址作為返回值來使用。這里主要是由函數里局部變量所存在的存儲區的類型所決定的。

  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;
}

  則:

  可見要深刻理解這句話:能不能返回局部指針變量,不在於這個指針變量的類型和性質(不在於該指針是不是局部指針變量),而在於該指針指向的對象的類型和性質。

 

 

 

  

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM