C語言記憶體管理(初級)—-動態數組

來源:互聯網
上載者:User

      C 語言提供的指標使我們可以直接操縱記憶體,在帶來巨大靈活性的同時也帶來了巨大的安全隱患。隨著程式規模的增大,管理記憶體的難度也大為增加,記憶體管理應該說是一項艱巨任務。

      C 語言引起的記憶體問題都是沒有正確使用和維護記憶體造成的,比如 C 語言初學者可能會寫出下面的代碼:

char *p;strcpy(p, "hello world!");

這樣的程式會直接崩潰掉,因為字元指標 p 未經初始化,其所儲存的值是不可預料的,它所指向的地址一般來說就不再是我們的程式有權使用的,那麼後面向這個指標所指向的記憶體複製字串,自然就會導致被作業系統給拒絕了,理由是使用未授權的記憶體,所以必須在複製前給 p 分配有效記憶體。

      C 語言提供了直接申請堆上的記憶體的函數: void *malloc(size_t n),使用這個函數要很小心,一是必須檢查分配是否成功,二是必須在這塊記憶體不再使用時使用 free 函數釋放掉,但痛點就是確定釋放的時間點。在這裡,我們以一些具體例子來描述可能產生的問題。

      第一個例子是初學者常犯的錯誤,他們可能會想通過一個函數來為指標分配記憶體並初始化,於是寫出這樣的代碼:

int malloc_space(char *dest, int n){      if (n <= 0) return -1;       dest = (char *)malloc(n * sizeof(char));      if (dest == NULL) return -2;       memset(dest, 0, n);           return 0;} int main(){      char *buffer = NULL;      malloc_space(buffer, 1024);      /* TODO: do something use buffer. */      return 0;}

但是這段代碼會讓他們困惑,因為程式總是在使用 buffer 的時候崩潰掉,通過跟蹤調試會發現,在執行 malloc_space 函數之後,指標 buffer 的值仍是 0 (如果你在定義 buffer 的時候未初始化為 NULL,則此時 buffer 是一個隨機的地址,你就更難發現程式錯誤的根源了),這說明 malloc_space 並未能夠給 buffer 分配到記憶體,可是你會發現 malloc_space 是正常執行了的,沒有發生錯誤情況,記憶體的分配也是成功了的。其實這裡的關鍵問題在於函數調用的時候,形參會成為實參的一個副本,所以這裡你實際上是為形參
dest 分配的記憶體,而沒能為 buffer 分配記憶體, buffer 依舊是 NULL。解決問題的兩種思路,一是採用二級指標,即指標的指標,把函數 malloc_space 改成這樣

int malloc_space(char **dest, int n){      if (n <= 0) return -1;       *dest = (char *)malloc(n*sizeof(char));      if (*dest == NULL) return -2;       memset(*dest, 0, n);           return 0;}

使用的時候需要把指標的地址傳給它:

int i = 0;char *buffer = NULL;i = malloc_space(&buffer, 1024);if (i != 0){      /* Error:.... */} /* OK, do something use buffer. */

另一種辦法是在函數 malloc_space 裡分配到記憶體後,把這塊記憶體的首地址直接作為傳回值:

void *malloc_space(int n){      void *dest = NULL;       if (n <= 0) return NULL;       dest = malloc(n);      if (dest == NULL) return NULL;       memset(dest, 0, n);           return dest;}

然後讓 buffer 接受它的傳回值就可以了:

char *buffer = NULL;buffer = (char *)malloc_space(1024);if (buffer == NULL){      /* Error: no mmemory... */} /* OK, do something use buffer. */

      接下來我們考慮一個完整的例子: 建立並銷毀二維的動態數組,這個在處理矩陣的時候會很有用,因為 C 語言在定義數組的時候必須給定維度,但如果你寫一個矩陣乘法的函數,你總不會希望你的程式只能適用於固定行數和列數的矩陣吧。我們就來實現這個動態二維數組,首先需要開闢一個一維數組,用來存放矩陣每一行的首地址,第0個元素存放矩陣第0行的首地址,第1個元素存放矩陣第1行的首地址,依此類推。然後再為這個數組的每個元素分配一個一維數組以儲存矩陣的每一行。借用前面的實現思路,我們實現一個函數來完成此任務:

int **create_array_2d(int row, int colume){      int **dest = NULL;       if (row <= 0 || colume <= 0) return NULL;       dest = (int **)malloc(row * sizeof(int *));      if (dest == NULL) return NULL;       memset(dest, 0, row * sizeof(int *));       return dest;}

現在指標 dest 已經分到了一個一維數組的空間,不過每個元素都是一個指標(int *),現在需要讓這每一個指標都分到一個一維數組(元素是int)以便儲存矩陣的每一行,於是繼續改造函數 create_array_2d:

int **create_array_2d(int row, int colume){      int **dest = NULL;      int i = 0;       if (row <= 0 || colume <= 0) return NULL;       dest = (int **)malloc(row * sizeof(int *));      if (dest == NULL) return NULL;       memset(dest, 0, row * sizeof(int *));       for (i = 0; i < row, i++)      {            dest[i] = (int *)malloc(colume * sizeof(int));            if (dest[i] == NULL) return NULL;              memset(dest[i], 0, colume * sizeof(int));      }       return dest;}

這個函數在每一次分配記憶體都成功的情況下,將為一維數組 dest 的每一個元素(int *) 分配到 colume 個整數的空間,於是它正好可以容納 row * colume 個整數,最關鍵的是,它可以使用 a[i][j] 的方式來訪問矩陣中的元素,這看起來似乎 dest 就是矩陣本身一樣,這顯然對於代碼的可讀性是有益的。但是這裡有一個極其嚴重的問題,在上面這個函數的 for 迴圈內,為 dest 的每一個元素(int *)分配記憶體都是有可能失敗的,如果在為 dest[1]、dest[2]、dest[3] 分配記憶體時都成功,但在為
dest[4] 分配記憶體時失敗了,顯然 dest[1]、dest[2]、dest[3] 已經分到的記憶體是應該要釋放掉的,但這裡卻直接返回一個null 指標就結束了,這顯然造成了嚴重的記憶體流失,因此這個函數需要修正如下(注意 for 迴圈裡添加的嵌套 for 迴圈):

int **create_array_2d(int row, int colume){      int **dest = NULL;      int i = 0, j = 0;       if (row <= 0 || colume <= 0) return NULL;       dest = (int **)malloc(row * sizeof(int *));      if (dest == NULL) return NULL;       memset(dest, 0, row * sizeof(int *));       for (i = 0; i < row, i++)      {            dest[i] = (int *)malloc(colume * sizeof(int));            if (dest[i] == NULL)            {                  for (j = 0; j < i; j++)                  {                        free(dest[j]);                        dest[j] = NULL;                  }                  free(dest);                  dest = NULL;                  return NULL;            }              memset(a[i], 0, colume * sizeof(int));      }       return dest;}

這裡需要提醒的是最好養成一些良好的習慣,記憶體配置成功後立即初始化,記憶體釋放後立即把相應的指標置為 NULL,以防止所謂的“野指標”問題。現在我們的主函數裡就可以這樣建立矩陣:

int rows = 10, columes = 6int **matrix = create_array_2d(rows, columes);if (matrix == NULL){      /* error: no memory... */}/* do something... */

在 create_array_2d 執行成功後,就可以為 matrix 所代表的二維數組賦值了: matrix[i][j] = ...,在完成你的任務後,我們還需要來釋放掉 matrix 所代表的二維數組,注意千萬不能直接 free(matrix) 這樣的方式來釋放,因為這隻是釋放了 matrix 這個一維數組(元素是 int *)的空間,而它的各個元素所指向的空間卻沒有釋放,正確的方式是

int destroy_array_2d(int ***a, int row, int colume){      int i = 0;      if (row <= 0 || colume <= 0) return -1;      for (i = 0; i < row; i++)      {            free((*a)[i]);            (*a)[i] = NULL;      }      free(*a);      *a = NULL;      return 0;}

這段代碼可能有點難讀,不知讀者還對前面通過一個函數來為指標分配記憶體不成功有印象沒有,如果你想改變傳入的實參指標的值,你就必須傳遞指標的指標,否則它改變的只是形參指標,所以我們剛才在分配記憶體的時候採用的傳回值的方式而非傳參數的方式,但現在釋放指標必須是傳遞參數,既然要修改二級指標的值(需要置為 NULL),就需要傳遞三級指標,當然代價是可讀性變差了,但這是沒有辦法的事情。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.