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

<全文完> 内存

相关文章
相关标签/搜索