函數間傳遞動態記憶體,C語言函數間動態記憶體的傳遞詳解

2020-07-16 10:04:22
跨函數使用動態記憶體很重要。所謂“跨函數使用動態記憶體”就是指“如何在主調函數中使用被調函數中動態分配的記憶體”。前面章節我們介紹了指標,其目的一是為了講“動態記憶體分配”。第二個目的就是為了講“跨函數使用動態記憶體”。

下面來寫一個程式:
# include <stdio.h>
# include <stdlib.h>
void DynamicArray(int **q);  //函數宣告
int main(void)
{
    int *p = NULL;
    DynamicArray(&p);  //函數呼叫
    printf("*p = %dn", *p);
    return 0;
}
void DynamicArray(int **q)  //DynamicArray是“動態陣列的意思”
{
    *q = malloc(sizeof*q);
    **q = 5;
    return;
}
輸出結果是:
*p = 5

程式說明:
1) “int*p;”表示定義了一個 int* 型的指標變數 p,它只能指向 int 型變數,裡面只能存放 int 型變數的地址,但此時它裡面還沒有內容,也就是說還沒有初始化。那麼 p 是什麼時候被初始化的?當呼叫完 DynamicArray 函數後,DynamicArray 函數構建了一個動態的記憶體空間,且 p 指向了這個記憶體空間,此時 p 才被初始化。

2) p 雖然是指標變數,但指標變數也是變數,只要是變數,在程式執行時系統就會為其分配記憶體單元,所以 p 也有自己的地址。系統為 p 分配記憶體單元是自動的,而給 p 初始化卻是程式設計師的事。所以不要把“p 的地址”和“p 裡面存放的別的變數的地址”給弄糊塗了。

3) 函數呼叫時為什麼傳遞的是 &p 而不是 p?我們可以試一試把實參改成 p,看看會怎麼樣:
# include <stdio.h>
# include <stdlib.h>
void DynamicArray(int *q);  //函數宣告
int main(void)
{
    int i = 2;
    int *p = &i;
    DynamicArray(p);  //函數呼叫
    printf("*p = %dn", *p);
    return 0;
}
void DynamicArray(int *q)
{
    q = malloc(sizeof*q);
    *q = 5;
    return;
}
輸出結果是:
*p = 2

首先,實參改成了 p,那麼形參就不能再寫 int**q 了。因為 p 是 int* 型,所以 q 也必須是 int* 型。同理,“*q=malloc(sizeof*q);”中賦值號左邊的 q 前面的 * 也要掉。“**q=5;”也要改成“*q=5;”。

此時指標變數 p 指向變數 i,那麼指標變數 q 也指向變數 i,即 q 中存放i的地址。但隨後構建了一個動態記憶體,且 q 指向這個記憶體。q 中原本存放的i的地址被新的地址取代了,此時 q 不再指向 i,所以 *q 中的值的改變不會影響 i 的值。所以程式最後執行的結果 *p 還是等於 2。

這個實際上同前面講的普通變數的傳遞是一樣的。對於普通變數,如果想在被調函數中直接修改主調函數中變數的值,那麼就必須要傳遞該變數的地址。對於指標變數也是一樣的,如果想在被調函數中修改主調函數中定義的指標變數的指向,那麼也要傳遞該指標變數的地址。因為修改指標變數的指向就是修改指標變數的值。

4) 為什麼形參是 int**q,而不是 int*q?同樣可以用兩種方式理解:
  1. 因為實參傳遞的是“指標變數的地址”,而指標變數的地址是指標的指標,基本類型為int*型,所以形參的基本類型也必須是int*型才能進行傳遞。
  2. 因為指標變數p的型別是int*型,所以&p的型別為int**型,所以形參也必須是int**型才能進行傳遞。

5) 從該程式中也可以看出,動態分配的記憶體在函數呼叫結束後並沒有被釋放。通過輸出結果可以看出,它裡面存放的仍然是 5。這就是動態記憶體分配。

多級指標就是比較“繞”,難度其實不是很大。到底哪個存放的是哪個的地址,這個如果你一開始想不清楚的話可以用筆在紙上畫一下。

最後需要講的是,“跨函數使用動態記憶體”也可以不用多級指標,即直接返回被調函數中指向動態記憶體的指標變數,然後賦給主調函數中的指標變數就行了。下面把函數寫下來。
# include <stdio.h>
# include <stdlib.h>
int * DynamicArray(void) ;  //函數宣告
int main(void)
{
    int *p = DynamicArray();  //函數呼叫
    printf("*p = %dn", *p);
    return 0;
}
int * DynamicArray(void)  //DynamicArray是“動態陣列的意思”
{
    int *q = malloc(sizeof*q);
    *q = 5;
    return q;
}
輸出結果是:
*p = 5

這種方式相比前面使用多級指標的方式更好理解。而且在實際程式設計中,多是使用這種方式。但是需要注意的是,只有動態分配的記憶體空間的地址才能返回。這個程式中指標變數 q 所指向的記憶體空間是用 malloc 定義的,即動態分配的。所以函數呼叫結束後這段記憶體空間也不會被釋放,因此返回它的地址才有意義。若 q 所指向的記憶體空間不是動態分配,而是在棧中靜態分配的,那麼就不能返回它的地址。因為函數呼叫結束後這段記憶體空間已經被釋放了,不能使用,所以返回去也沒有意義。

因此,在 C 語言中,在講動態記憶體分配之前經常有一句話,叫作“永遠不要返回區域性變數的地址”。