C语言中的动态内存管理

本文介绍了动态内存管理的必要性,探讨了malloc、calloc和realloc等内存分配函数,以及它们的使用和可能产生的问题,包括未判空、误释放非动态内存和内存泄漏。同时分析了C语言中关于动态内存的经典笔试题示例。
摘要由CSDN通过智能技术生成

目录

为什么要有动态内存管理?

内存分配函数

常见的动态内存错误

动态内存经典笔试题分析 


为什么要有动态内存管理?

C语言的数据结构的大小通常是固定的。例如,一旦程序完成编译,数组元素就固定了。[ 从C99开始,变长数组的长度运行时确定,但在数组的生命周期内仍是固定长度的。] 在编写程序时就强制了大小,这样很可能会导致问题。例如,我们在实现一个通讯录时,一开始就把联系人的最大个数限制在了100人。有两种情况,第一,我们根本就不需要那么大的空间,也就是说没有100个联系人那么多,只有50个左右,这样就导致了空间的浪费。第二,这100个联系人的空间压根就不够用。动态内存分配的出现,程序员便可以设计出能根据需要扩大或缩小的数据结构。

内存分配函数

为了动态分配存储空间,需要调用3中内存分配函数的一种,这些函数都是声明在<stdlib.h>头中的。

> malloc函数——分配内存块,但是不对内存块进行初始化。

> calloc函数——分配内存块,并且将内存块初始化为0。

> realloc函数——调整动态开辟的内存块大小。

void *malloc( size_t size );

void *malloc( size_t size );

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

相对于calloc函数,malloc函数较为常用。因为malloc函数不需要对分配的内存块进行初始化,所以它比calloc函数更高效。这些函数都是在堆上开辟空间的。

当为申请内存块而调用内存分配函数时,因为函数无法知道计划存储的数据类型,所以会返回void*的指针,它本质上只是内存地址,我们只需根据需要进行强制类型转换后使用即可。当然,总存在这样的可能性:找不到满足我们需要的足够大的内存块。那么这些函数将会返回空指针NULL,因此我们有必要对函数的返回值进行判空。还值得一提的是,在使用realloc函数时,存在以下两种情况。第一,原有空间后没有足够大的空间;第二,原有空间后有足够大的空间。

当原有空间之后没有足够大的空间时,扩展的方法是:在堆上找一个合适大小的连续空间来使用,并将原空间的数据拷贝到新空间,同时自动释放掉原有空间,返回新空间的地址。

在使用malloc、calloc函数开辟空间是,会不可避免的使用到另一个相关的函数——free。

void free( void *memblock );

使用free函数很容易,只需要把指向不再需要的内存块的指针传给free函数即可。但是使用此函数会导致一个新的问题—— “悬空指针”问题。调用free(p)函数会释放p指向的内存块,但是不会改变p本身,因为传给free函数的p只是一个变量,并不是传地址,形参与实参分别占用不同的空间,对形参的改变是不会影响实参p的。如果后续对p进行strcpy(p, "hello world");等操作,会导致错误。为了规避这种错误,在free后要对指针进行置空p = NULL,后续在使用时也要判断指针是否有效。

常见的动态内存错误

一:没有判断malloc的返回值是否为NULL,free释放空间后没有置空

void test()

{

       int* p = (int*) malloc(INT_MAX);

       *p = 20;

       free(p);

}

二:对非动态开辟内存使用free释放

viod test()

{

       int i = 9;

       int* p = NULL;

       *p = i;

       //写了很多行代码后,写醉了

       free(p) ;

       p = NULL;

}

三:使用free释放一块动态开辟内存的一部分

void test()

{

       int* p = (int*) malloc(100);

       if(p == NULL)

       {

            perror("malloc");

            return;

       }

       p++;//不再指向起始位置

       free(p);

       p = NULL;

四:动态开辟内存忘记释放(内存泄漏)

#include <stdio.h>
#include <stdlib.h>

void test()
{
    int* p = (int*)malloc(100);
    if (p != NULL)
    {
        *p = 99;
    }
}
int main()
{
    test();
    while (1);//不让程序停下来
    return 0;
}

调用test函数结束后,变量p会被销毁,申请的100个字节的空间将不会被访问到。对于程序而言,不可再访问到的内存块被称作垃圾。留有垃圾的程序存在内存泄漏现象,会不断地消耗内存。

动态内存经典笔试题分析 

题目一:

void GetMemory(char* p)
{
	p = (char*)malloc(100);
}
void Test(void)
{
	char* str = NULL;
	GetMemory(str);
	strcpy(str, "hello world");
	printf(str);
}

请问运行Test函数会有什么样的结果?

> 达不到打印hello world的预期效果,实参str和形参p占用不同的空间,对形参p的改变不会影响实参str,所以str始终放的是空指针NULL,strcpy函数内部会对str进行解引用操作,即对空指针NULL的解引用,可能会导致程序崩溃。

> 该程序存在内存泄漏,在函数GetMemory调用结束后,形参p会被销毁,但p指向的动态开辟空间并未释放,对于程序来说,该空间就是垃圾,会导致内存泄漏现象。

> 说明一下,此处printf的使用并未错误,printf("haha");没问题这是共识,字符串"haha"的结果为首元素h的地址,str也是地址,既然前者没问题,那么printf(str);自然合理。

题目二:

char* GetMemory(void)
{
	char p[] = "hello world";
	return p;
}
void Test(void)
{
	char* str = NULL;
	str = GetMemory();
	printf(str);
}

请问运行Test函数会有什么样的结果?

>  达不到打印hello world的预期效果,可能会打印乱码。

> 数组p是在栈上开辟的,没有用到内存开辟函数malloc和calloc,不会涉及到堆区,在函数GetMemory调用结束后,数组p会被销毁,即使已经返回了数组的起始地址给str,但是str所指向的空间不再属于这个程序,使用str指向的空间就会导致非法访问,打印乱码。

题目三:

void GetMemory(char** p, int num)
{
	*p = (char*)malloc(num);
}
void Test(void)
{
	char* str = NULL;
	GetMemory(&str, 100);
	strcpy(str, "hello");
	printf(str);
}

请问运行Test函数会有什么样的结果?

> 能正常打印hello,但是没有释放动态开辟的内存。


注:本篇博客参考 [美] K.N.King 著 吕秀锋 黄倩 译 李忠 审校《C语言程序设计现代方法》第2版 林锐 韩永泉 编著《高质量程序设计指南》第3版

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值