动态内存管理及常见错误malloc()、realloc()、calloc()、free()
引入
在确定用哪种存储类别后,根据已制定好的内存管理规则,将自动选择其作用域和存储期。然而,还有更灵活地选择,即用库函数分配和管理内存。
首先,回顾一下内存分配。所有程序都必须预留足够的内存来储存程序使用的数据。这些内存中有些是自动分配的。例如,以下声明:
int plates[100];
该声明预留了100个内存位置,每个位置都用于储存int类型的值。声明还为内存提供了一个标识符。因此,可以使用x或place识别数据。回忆一下,静态数据在程序载入内存时分配,而自动数据在程序执行块时分配,并在程序离开该块时销毁。
动态内存函数介绍
malloc()和free()
viod* calloc(size_t num, size_t size);
malloc()函数,该函数接受一个参数:所需的内存字节数。malloc()函数会找到合适的空闲内存块,这样的内存是匿名的。也就是说,malloc()分配内存,但是不会为其赋名。然而,它确实返回动态分配内存块的首字节地址。因此,可以把该地址赋给一个指针变量,并使用指针访问这块内存。因为char表示1字节,malloc()的返回类型通常被定义为指向char的指针。然而,从ANSI C标准开始,C使用一个新的类型:指向void的指针。该类型相当于一个“通用指针”。malloc()函数可用于返回指向数组的指针、指向结构的指针等,所以通常该函数的返回值会被强制转换为匹配的类型。在ANSI C中,应该坚持使用强制类型转换,提高代码的可读性。然而,把指向 void的指针赋给任意类型的指针完全不用考虑类型匹配的问题。如果 malloc()分配内存失败,将返回空指针。
我们试着用 malloc()创建一个数组。除了用 malloc()在程序运行时请求一块内存,还需要一个指针记录这块内存的位置。例如,考虑下面的代码:
double * ptd;
ptd = (double *) malloc(30 * sizeof(double));
以上代码为30个double类型的值请求内存空间,并设置ptd指向该位置。
注意,指针ptd被声明为指向一个double类型,而不是指向内含30个double类型值的块。回忆一下,数组名是该数组首元素的地址。因此,如果让ptd指向这个块的首元素,便可像使用数组名一样使用它。也就是说,可以使用表达式ptd[0]访问该块的首元素,ptd[1]访问第2个元素,以此类推。根据前面所学的知识,可以使用数组名来表示指针,也可以用指针来表示数组。
现在,我们有3种创建数组的方法:
1.声明数组时,用常量表达式表示数组的维度,用数组名访问数组的元
素。可以用静态内存或自动内存创建这种数组。
2.声明变长数组时,用变量表达式表示数组的维度,用数组名访问数组的元素。具有这种特性的数组只能在自动内存中创建。
3.声明一个指针,调用malloc(),将其返回值赋给指针,使用指针访问数组的元素。该指针可以是静态的或自动的。
通常,malloc()要与free()配套使用。free()函数的参数是之前malloc()返回的地址,该函数释放之前malloc()分配的内存。因此,动态分配内存的存储期从调用malloc()分配内存到调用free()释放内存为止。设想malloc()和free()管理着一个内存池。每次调用malloc()分配内存给程序使用,每次调用free()把内存归还内存池中,这样便可重复使用这些内存。free()的参数应该是一个指针,指向由 malloc()分配的一块内存。不能用 free()释放通过其他方式(如,声明一个数组)分配的内存。
注意,free()函数位于程序的末尾,它释放了malloc()函数分配的内存。free()函数只释放其参数指向的内存块。一些操作系统在程序结束时会自动释放动态分配的内存,但是有些系统不会。为保险起见,请使用free(),不要依赖操作系统来清理。
使用动态数组有什么好处?假设你已经知道,在大多数情况下程序所用的数组都不会超过100个元素,但是有时程序确实需要10000个元素。要是按照平时的做法,你不得不为这种情况声明一个内含 10000 个元素的数组。基本上这样做是在浪费内存。如果需要10001个元素,该程序就会出错。这种情况下,可以使用一个动态数组调整程序以适应不同的情况。
free()的重要性
int main()
{
double glad[2000];
int i;
for (i = 0; i < 1000; i++)
gobble(glad, 2000);
for (i = 0; i < 1000; i++)
gobble(glad, 2000);
}
void gobble(double ar[], int n)
{
double * temp = (double *) malloc( n * sizeof(double));
.../* free(temp); // 假设忘记使用free() */
}
第1次调用gobble()时,它创建了指针temp,并调用malloc()分配了16000字节的内存(假设double为8 字节)。假设如代码注释所示,遗漏了free()。当函数结束时,作为自动变量的指针temp也会消失。但是它所指向的16000字节的内存却仍然存在。由于temp指针已被销毁,所以无法访问这块内存,它也不能被重复使用,因为代码中没有调用free()释放这块内存。
第2次调用gobble()时,它又创建了指针temp,并调用malloc()分配了16000字节的内存。第1次分配的16000字节内存已不可用,所以malloc()分配了另外一块16000字节的内存。当函数结束时,该内存块也无法被再访问和再使用。
循环要执行1000次,所以在循环结束时,内存池中有1600万字节被占用。实际上,也许在循环结束之前就已耗尽所有的内存。这类问题被称为内存泄漏(memory leak)。在函数末尾处调用free()函数可避免这类问题发生。
calloc
void* calloc(size_t num, size_t size);
典型的用法如下:
long * newmem;
newmem = (long *)calloc(100, sizeof (long));
和malloc()类似,calloc()也返回指向char的指针;在ANSI之后,返回指向void的指针。如果要储存不同的类型,应使用强制类型转换运算符。calloc()函数接受两个无符号整数作为参数(ANSI规定是size_t类型)。第1个参数是所需的存储单元数量,第2个参数是存储单元的大小(以字节为单位)。在该例中,long为4字节,所以,前面的代码创建了100个4字节的存储单元,总共400字节。
**用sizeof(long)而不是4,提高了代码的可移植性。这样,在其他long不是4字节的系统中也能正常工作。**calloc()函数还有一个特性:它把块中的所有位都设置为0(注意,在某些硬件系统中,不是把所有位都设置为0来表示浮点值0)。
realloc
void* realloc(void* ptr, size_t size);
有时候我们发现过去申请的空间太小,有时候我们又觉得申请的空间太大,为了合理地使用内存,我们一定会对内存做灵活的调整。该函数就是重新分配内存,把内存扩展到 newsize。
realloc在调用存储空间的是存在两种情况:
1.原有空间之后有足够的空间,要扩展内存就直接在原有空间的基础上直接追加空间,原来空间的数据不发生变化。
2.原有空间之后没有足够大空间,为了保持数据的连续性,系统会在内存中开辟一块新的内存空间来存放原有的数据,而原来额数据会在系统运行时系统自动收回,需要说明的是,函数返回的是新的内存地址。同样的,realloc()需要判断是否为空指针,需要进行free()。
常见的动态内存错误
1 对NULL的解引用操作
void test()
{
int *p = (int*)malloc(40);
//万一malloc失败,p就被赋值为NULL
*p = 0;//err
int i = 0;
for (i = 0; i < 10; i++)
{
*(p+i) = i;
}
free(p);
p = NULL;
}
2 对动态内存开辟的越界访问
void test()
{
int *p = (int*)malloc(5 * sizeof(int));
if (p == NULL)
{
return 0;
}
else
{
int i = 0;
for (i = 0; i < 10; i++)
{
*(p + i) = i;
}
}
free(p);
p = NULL;
}
3 对非动态开辟内存的free
void test()
{
int a = 10;
int* p = &a;
*p = 20;//栈区上的指针
free(p);//堆区上的指针
p = NULL;
return 0;
}
4 使用free释放一块动态内存开辟的一部分
void test()
{
int* p = (int*)malloc(100);
p++;
free(p);//p不再指向动态内存的起始位置
}
5 对同一块动态内存多次释放
void test()
{
int* p = (int*)malloc(100);
free(p);
free(p);//重复释放
}
6 动态内存忘记释放(内存泄漏)
void test()
{
int* p = (int*)malloc(100);
if (p == NULL)
return;
*p = 20;
}
推荐阅读
-
malloc,calloc,realloc及动态开辟内存常见错误
-
动态内存管理及常见错误malloc()、realloc()、calloc()、free()
-
C风格的动态内存管理(malloc、calloc、realloc、free)以及总结free崩溃常见的几种情况
-
对动态内存分配函数malloc、calloc、realloc、free的理解
-
动态内存管理-malloc/realloc/calloc/free和new/delete的区别
-
【c语言】动态内存管理(malloc、free、calloc、realloc)
-
C语言——内存管理(calloc、malloc、realloc、free)
-
C语言动态内存管理:malloc、realloc、calloc以及free函数
-
C语言动态内存分配——malloc,calloc,realloc,free
-
C语言中动态内存管理方式(malloc、calloc、realloc的区别)