《C编程.中级C》3.内存管理

在C中,您已经考虑过创建要在程序中使用的变量。您已经创建了一些要使用的数组,但您可能已经注意到了一些限制:

  • 必须事先知道数组的大小
  • 在程序的持续时间内无法更改数组的大小

C中的动态内存分配是一种规避这些问题的方法。

malloc函数

#include  <stdlib.h>
void  * calloc (size_t  nmemb , size_t  size ); 
void  free (void  * ptr ); 
void  * malloc (size_t  size ); 
void  * realloc (void  * ptr , size_t  size );

标准C函数malloc是实现动态内存分配的手段。它在stdlib.h或malloc.h中定义,具体取决于您可能使用的操作系统。Malloc.h仅包含内存分配函数的定义,而不包含stdlib.h中定义的其他函数的定义。通常你不需要在程序中如此具体,如果两者都受支持,你应该使用<stdlib.h>,因为那是ANSI C,我们将在这里使用。

将分配的内存释放回操作系统的相应调用是free

当不再需要动态分配的内存时,free应调用它以将其释放回内存池。覆盖指向动态分配的内存的指针可能导致该数据变得不可访问。如果经常发生这种情况,最终操作系统将无法再为该进程分配更多内存。一旦进程退出,操作系统就能够释放与该进程相关的所有动态分配的内存。

我们来看看动态内存分配如何用于数组。

通常,当我们希望创建一个数组时,我们使用一个声明,例如

int  array [ 10 ];

召回array可以被认为是我们用作数组的指针。我们指定此数组的长度为10 ints。之后array[0],其他九个整数有空间连续存储。

有时在编写程序时不知道某些数据需要多少内存。在这种情况下,我们希望在程序开始执行后动态分配所需的内存。要做到这一点,我们只需要声明一个指针,并malloc在我们希望为数组中的元素腾出空间时调用,或者,我们可以告诉malloc在第一次初始化数组时创建空间。这两种方式都是可以接受和有用的。

我们还需要知道int在内存中占用了多少才能为它腾出空间; 幸运的是,这并不困难,我们可以使用C的内置sizeof运算符。例如,如果sizeof(int)产量为4,则一个int占用4个字节。当然,2*sizeof(int)我们需要多少内存2 int秒,依此类推。

那么我们如何才能像以前那样使用mallocint几个数组呢?如果我们希望在一次打击中宣布并腾出空间,我们可以简单地说

int  * array  =  malloc (10 * sizeof (int ));

我们只需要声明指针; malloc给我们一些空间来存储10 int秒,并返回指向第一个元素的指针,该元素被分配给该指针。

重要的提示! malloc没有初始化数组; 这意味着该数组可能包含随机或意外的值!就像创建没有动态分配的数组一样,程序员必须在使用之前用合理的值初始化数组。一定要确保你这样做。(稍后看一下这个函数memset的简单方法。)

malloc在为已分配的内存声明指针后,无需立即调用。通常在声明和调用之间存在许多语句malloc,如下所示:

int  * array  =  NULL ; 
printf (“Hello World !!!” ); 
/ * more statements * / 
array  =  malloc (10 * sizeof (int ));  / *延迟分配* / 
/ *使用数组* /

错误检查

当我们想要使用时malloc,我们必须注意程序员可用的内存池是有限的。因此,我们可以想象内存不足!在这种情况下,malloc将返回NULL。为了阻止程序崩溃而不再使用内存,应该NULL在尝试使用内存之前始终检查malloc是否未返回; 我们可以做到这一点

int  * pt  =  malloc (3  *  sizeof (int )); 
if (pt  ==  NULL )
{ 
   fprintf (stderr , “Out of memory,exiting \ n ” ); 
   退出(1 ); 
}

当然,在上面的示例中突然退出并不总是合适的,并且取决于您要解决的问题以及您正在编程的架构。例如,如果程序是一个小的非关键应用程序,则在桌面退出时运行可能是合适的。但是,如果程序是在桌面上运行的某种类型的编辑器,您可能希望让操作员选择保存其繁琐输入的信息,而不是仅退出程序。嵌入式处理器中的存储器分配故障(例如可能在洗衣机中)可能导致机器的自动重置。因此,许多嵌入式系统设计人员完全避免动态内存分配。

calloc函数

calloc函数为项数组分配空间并将内存初始化为零。调用mArray = calloc( count, sizeof(struct V))分配count对象,每个对象的大小足以包含结构的实例struct V。空间初始化为所有位零。该函数返回指向已分配内存的指针,或者,如果分配失败,则返回NULL

realloc函数

 void  *  realloc  ( void  *  ptr , size_t  size  );

realloc函数将指向的对象的大小更改为ptr指定的大小size。物体的内容应保持不变,直至新旧尺寸中的较小者。如果新大小较大,则新分配的对象部分的值是不确定的。如果ptr是空指针,则该realloc函数的行为类似于malloc指定大小的函数。否则,如果ptr不匹配的指针早些时候返回callocmallocrealloc功能,或如果空间已经到呼叫释放freerealloc功能,该行为是不确定的。如果无法分配空间,则指向的对象ptr不变。如果size是零和ptr不是空指针,指向的对象被释放。该realloc函数返回空指针或指向可能移动的已分配对象的指针。

free函数

已经使用mallocrealloccalloc必须在不再需要时释放回系统内存池的内存。这样做是为了避免永久分配越来越多的内存,这可能导致最终的内存分配失败。free但是,当前程序在大多数操作系统上终止时,将释放未发布的内存。呼叫free如下例所示。

int  * myStuff  =  malloc ( 20  *  sizeof (int ));  
if  (myStuff  !=  NULL ) 
{ 
   / *更多语句* / 
   / *时间释放myStuff * / 
   free ( myStuff  ); 
}

免费的递归数据结构

应该注意的free是既不聪明也不递归。以下代码依赖于free的递归应用程序对结构的内部变量不起作用。

typedef  struct  BSTNode  
{ 
   int  value ;  
   struct  BSTNode *  left ; 
   struct  BSTNode *  right ; 
}  BSTNode ;

//稍后:...... 

BSTNode *  temp  =  (BSTNode * ) calloc (1 , sizeof (BSTNode )); 
temp - > left  =  (BSTNode * ) calloc (1 , sizeof (BSTNode ));

//稍后:...... 

免费(临时);  //错!不要这样做!

声明“ free(temp);” 不会释放temp->left,导致内存泄漏。正确的方法是定义一个释放数据结构中每个节点的函数:

void  BSTFree (BSTNode *  node ){ 
    if  (node  !=  NULL ) { 
        BSTFree (node - > left ); 
        BSTFree (节点- > 右); 
        free (node ); 
    } 
}

因为C没有垃圾收集器,所以C程序员负责确保free()每次都有一次malloc()。如果一次为一个节点分配了一个节点,则需要一次释放一个节点。

不要释放未定义的指针

此外,free当首先从未分配有问题的指针时经常崩溃或导致进一步的神秘错误。

要避免此问题,请始终在声明指针时初始化指针。要么malloc在声明它们时使用(如本章中的大多数示例所示),要么NULL在声明它们时将它们设置为(在本章的“延迟分配”示例中)。 [1]

编写构造函数/析构函数

获得内存初始化和破坏的一种方法是模仿面向对象的编程。在这个范例中,对象是在为它们分配原始内存,过它们的生命之后构建的,当它们被销毁时,一个称为析构函数的特殊函数在对象本身被销毁之前就会破坏对象的内部。

例如:

#include  <stdlib.h> / *需要malloc和朋友* /

/ *这是我们拥有的对象类型,只有一个int成员* / 
typedef  struct  WIDGET_T  { 
  int  member ; 
}  WIDGET_T ;

/ *处理WIDGET_T的函数* /

/ *构造函数* / 
void 
WIDGETctor  (WIDGET_T  * this , int  x )
{ 
  this - > member  =  x ; 
}

/ *析构函数* / 
void 
WIDGETdtor  (WIDGET_T  * this )
{ 
  / *在这种情况下,我真的不需要做任何事情,但
     如果WIDGET_T有内部指针,它们指向的对象
     将在这里被销毁。* / 
  this - > member  =  0 ; 
}

/ * create function  - 此函数返回一个新的WIDGET_T * / 
WIDGET_T  * 
WIDGETcreate  (int  m )
{ 
  WIDGET_T  * x  =  0 ;

  x  =  malloc  (sizeof  (WIDGET_T )); 
  if  (x  ==  0 )
    abort  ();  / *无内存* / 
  WIDGETctor  (x , m ); 
  返回 x ; 
}

/ * destroy函数 - 调用析构函数,然后释放对象* / 
void 
WIDGETdestroy  (WIDGET_T  * this )
{ 
  WIDGETdtor  (this ); 
  免费 (这); 
}

/ *结束代码* /

猜你想读:《C编程.中级C》4.错误处理

THE END
分享