C语言内存管理(初级)----动态数组

来源:互联网 发布:红米3打开网络没有网络 编辑:程序博客网 时间:2024/06/04 19:11

      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] 已经分到的内存是应该要释放掉的,但这里却直接返回一个空指针就结束了,这就造成了严重的内存泄漏,因此这个函数需要修正如下(注意 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),就需要传递三级指针,当然代价是可读性变差了,但这是没有办法的事情。

原创粉丝点击