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

news/2024/7/20 12:46:54 标签: 内存管理, c/c++, python

2019独角兽企业重金招聘Python工程师标准>>> hot3.png

      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 = 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),就需要传递三级指针,当然代价是可读性变差了,但这是没有办法的事情 ,因为释放内存只能采用传参的方式,无法采用像分配内存时的返回指针值的方式。

<全文完>

转载于:https://my.oschina.net/zhcosin/blog/94583


http://www.niftyadmin.cn/n/652528.html

相关文章

YModem协议

YModem协议&#xff1a; YModem协议是由XModem协议演变而来的&#xff0c;每包数据可以达到1024字节&#xff0c;是一个非常高效的文件传输协议。 下面先看下YModem协议传输的完整的握手过程&#xff1a;先看下图 SENDER:发送方。 RECEIVER:接收方。 第一步先由接收方&#x…

我的简单网络爬虫

断断续续地学习了一段时间的Python&#xff0c;把我的网络爬虫程序写在下面&#xff0c;供自己查阅复习吧&#xff01; # -*- coding: cp936 -*- import urllib page 1 head http://blog.sina.com.cn/s/articlelist_1191258123_0_ end .html i 0 while page < 7:url he…

IT真的不如卖爆米花的?

今晚去看电影&#xff0c;突然出现在我面前一个熟悉的身影“爆米花25一大份。。。”一个中年男递给我一张爆米花、饮料宣传单&#xff0c;仔细回忆才发现原来这是我07年实现的那家公司的老板&#xff0c;之前在电脑市场有2家IT配件门面&#xff0c;几年过去既然转行转的这么彻底…

Python适合自己的IDE才是最好的IDE

概述 “工欲善其事&#xff0c;必先利其器”&#xff0c;如果说编程是程序员的手艺&#xff0c;那么IDE就是程序员的吃饭家伙了。 IDE的全称是Integration Development Environment&#xff08;集成开发环境&#xff09;&#xff0c;一般以代码编辑器为核心&#xff0c;包括一系…

实用小软件

实用小软件 1、虚拟打印机&#xff08;64位可用&#xff09;http://pan.baidu.com/share/link?shareid166202&uk956582885 2、FastStone Capture(截图工具)http://pan.baidu.com/share/link?shareid166206&uk956582885 3、迅雷5绿色版http://pan.baidu.com/share/lin…

再次调试STM32F407+DP83848

早在还没有毕业前&#xff0c;就调试过STM32F407DP83848&#xff0c;这次又调试了一次&#xff0c;居然花了2天时间。STM32支持两种工业级标准的接口&#xff0c;来与外部物理层 PHY模块相连&#xff0c;分别是独立于介质的接口&#xff08;MII&#xff09;和简化的独立于接口的…

EPANET源码中用到的几个简单C语言函数介绍一

本文章用于没有太多C语言知识的读者&#xff0c;想要读懂开源的水力建模软件EPANET时&#xff0c;会遇到如下的一些C标准函数,现介绍如下&#xff1a; 一、fgets函数 原型&#xff1a; char *fgets(char *s, int n, FILE *stream); 参数&#xff1a; *s: 字符型指针&#xf…

cnbloger: 范振勇, 虚拟机上安装centos8.0[(vmware-15-pro)]

If the author of the article is not allowed to reprint, this article will be deleted 虚拟机上安装centos8.0&#xff1a; https://www.cnblogs.com/fanzhenyong/p/11616192.html 虚拟机上安装centos8.0 目录 一、准备宿主机 1.1、准备安装包 1.2、配置网络 1.2.1 查看…