C語言內存管理(初級)----動態數組

      C 語言提供的指針使咱們能夠直接操縱內存,在帶來巨大靈活性的同時也帶來了巨大的安全隱患。隨着程序規模的增大,管理內存的難度也大爲增長,內存管理應該說是一項艱鉅任務。 數組

      C 語言引發的內存問題都是沒有正確使用和維護內存形成的,好比 C 語言初學者可能會寫出下面的代碼: 安全

char *p;
strcpy(p, "hello world!");
這樣的程序會直接崩潰掉,由於字符指針 p 未經初始化,其所存儲的值是不可預料的,它所指向的地址通常來講就再也不是咱們的程序有權使用的,那麼後面向這個指針所指向的內存複製字符串,天然就會致使被操做系統給拒絕了,理由是使用未受權的內存,因此在複製以前必須給 p 分配有效的內存。

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

      第一個例子是初學者常犯的錯誤,他們可能會想經過一個函數來爲指針分配內存並初始化,因而寫出這樣的代碼: spa

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] 已經分到的內存是應該要釋放掉的,但這裏卻直接返回一個空指針就結束了,這顯然形成了嚴重的內存泄漏,所以這個函數須要修正以下(注意 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 = 6
int **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),就須要傳遞三級指針,固然代價是可讀性變差了,但這是沒有辦法的事情 ,由於釋放內存只能採用傳參的方式,沒法採用像分配內存時的返回指針值的方式。 code

<全文完> 內存

相關文章
相關標籤/搜索