栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

【C语言详解】——动态内存管理

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

【C语言详解】——动态内存管理

        导言:对于内存开辟来说,我们可以通过创建数组来开辟一片连续的内存。但是我们发现这并不能很好的满足要求,比如说通讯录中你有100个联系人需要存储,但你固定的开辟1000大小的数组,那就会造成内存浪费,因此,为了能更好的利用内存,我们也应该学会动态内存的管理。

目录

1 动态内存分配 VS 静态内存开辟

2 动态内存函数的介绍

2.1 malloc 和 free

2.2 malloc 和 free 的使用

2.3 calloc 函数

2.4 realloc 函数

2.5 小结

3 常见的动态内存错误

3.1 对NULL指针的解引用操作

3.2 对动态开辟空间的越界访问

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

3.4 使用free释放一块动态开辟内存的一部分or忘记释放

3.5 对同一块动态内存多次释放

4 几个经典的面试题

4.1 题目1

4.2 题目2

4.3 题目3

5 柔性数组(暂略)

1 动态内存分配 VS 静态内存开辟

我们已经掌握的内存开辟方式有:申请栈区开辟

int a = 20;
//在栈空间上开辟四个字节
char arr[10] = {0};
//在栈空间上开辟10个字节的连续空间

对于上述的开辟空间的方式有两个特点:

1.空间开辟大小是固定的

2.数组在申明的时候,必须指定数组的长度,它所需要的内存在编译时分配

综上:有时候可能需要在程序运行的时候才知道所需的空间,那这种空间的开辟方式就不能满足了

2 动态内存函数的介绍

所需要的头文件:#include

2.1 malloc 和 free
malloc 函数:
void* malloc (size_t size);
功能介绍:向内存的堆区申请开辟一块连续可用的空间。
函数的参数: 想要申请的空间大小【单位:字节】
函数的返回值: 返回的是指向这块空间起始地址的指针,指针类型为void*(泛型指针)
如果malloc 开辟空间成功,则返回一个指向开辟好空间起始地址的指针
如果malloc 开辟空间失败,则返回一个NULL指针

free 函数:
void free (void* ptr);
功能介绍:释放动态开辟的内存。

 有了以上的了解,我们可以得出以下结论:

        我们知道,以前我们定义的局部变量、函数、数组所需要的空间都是在栈上开辟的,但切记malloc申请的内存是在堆上开辟的。栈上的可用资源远远小于堆上的,所以malloc的专长是用来提供大内存需求,而且方便管理。malloc开辟成功时返回类型为void*,是一个泛型指针,可根据需要强制转换成想要的类型。malloc开辟失败时返回值为NULL,所以我们需要对返回值做出检查,以防非法访问内存的现象出现。

        注意:如果参数 size 为0,malloc的行为是标准是未定义的,取决于编译器。

        同理对于free函数,如果参数 ptr 指向的空间不是动态开辟的,那free函数的行为是未定义的。

int main()
{
	int a = 10;
	int* p = &a;
	free(p);     //不允许释放静态开辟的内存
	p = NULL;
	return 0;
}

 

        如果参数 ptr 是NULL指针,则函数什么事都不做。若没有对开辟的空间进行及时的释放,则也会造成内存泄漏的问题,那这块空间就会一直占用着内存,浪费空间。但值得注意的是在一段代码中,没有free函数并不意味着内存空间就不回收了。而是当程序退出的时候,系统会自动回收内存空间。因此我们也知道,堆区的空间不是无限制开辟的,是有限的。

        关于栈区和堆区,详见下面内存布局的理解:

 

1. 栈区(stack):

  • 在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。
  • 栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。
  • 栈区主要存放运行函数而分配的局部变量、函数参数、返回数据、返回地址等。

2. 堆区(heap):

  • 一般由程序员分配释放; 若程序员不释放,程序结束时可能由OS回收 。
  • 分配方式类似于链表

3. 数据段(静态区)(static):

  • 存放全局变量、静态数据
  • 程序结束后由系统释放

4. 代码段:

  • 存放函数体(类成员函数和全局函数)的二进制代码

 综上: 对于动态开辟的空间,使用完后记得及时释放。

2.2 malloc 和 free 的使用

示例如下:

int main()
{
	int arr[10] = { 0 };		//静态开辟写法
	int* p = (int*)malloc(40);	//动态内存开辟写法
	if (p == NULL)				//判断p指针是否为空
	{
		printf("%sn",strerror(errno));
		return 1;				//习惯返回值为1代表异常
	}
	//使用
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		*(p + i) = i;
	}
	for (i = 0; i < 10; i++)
	{
		printf("%d ",*(p + i));
	}
	free(p);					//释放p所指向的动态内存
	p = NULL;					//是否有必要?
	return 0;
}

        由上图很容易知道,如果只是用了free函数,那么该指针所指向的空间被释放后,该指针仍然存在,很容易造成非法访问内存。我们应该主动的将这个指针置为NULL,以防止非法访问内存。

小技巧:调试窗口中想查看指针的成员,可以输入 p,10

2.3 calloc 函数
void* calloc (size_t num, size_t size);
功能介绍:与malloc()函数的区别只在于, calloc()函数会在返回地址之前将所申请的内存空间中的每个字节都初始化为0。
参数上,第一个参数是你想要开辟的元素的个数,第二个参数是每个元素对应的字节大小。 
int main()
{
	int *p = (int*)calloc(10, sizeof(int));
	if (p == NULL)
		return 1;
	int i = 0;
	for (i = 0; i < 10; i++)
		printf("%d ", *(p + 1));
	free(p);
	p = NULL;
	return 0;
}

2.4 realloc 函数
void* realloc (void* ptr, size_t size);
功能介绍:realloc 是动态内存管理的灵魂之处,因为它可以真正实现动态内存的实现。
参数介绍:ptr是要调整的内存地址,size为调整之后的新大小;返回值为调整之后的内存起始位置;这个函数调整原内存空间大小的基础上,还会将原来内存中的数据移动到新的空间;
工作原理:
int main()
{
	int* p = (int*)malloc(40);
	if (p == NULL)
	{
		printf("%sn", strerror(errno));
		return 1;
	}
	//使用
	//1 2 3 4 5 6 7 8 9 10
	int i = 0;
	for (i = 0; i < 10; i++)
		*(p + i) = i + 1;
	//扩容
	//p = realloc(ptr, 80);     //第一种方法(不推荐)可能会申请空间失败造成内存泄漏,把原指针置空。
	int* ptr = (int*)realloc(p, 80);  //第二种方法扩展
	if (p != NULL)
		p = ptr;
	//使用
	for (i = 0; i < 10; i++)
		printf("%d ", *(p + i));
	free(p);
	p = NULL;
	return 0;
}

 拓展名词:内存池(略)

2.5 小结

1.malloc()和calloc()函数用法一样, 唯一的区别是calloc()会对所申请内存的每个字节初始化为0

2.malloc(), calloc(), realloc()申请的内存不再使用时 ,一定要用free()释放 ,否则会造成内存泄漏

3.p = realloc(ptr, size)函数返回值不为空时, 释放内存时不需写free(ptr) ,只需写free(p)

4.realloc(NULL,40);  //等价于malloc(40);

3 常见的动态内存错误

3.1 对NULL指针的解引用操作
void test()
{
	int *p = (int *)malloc(INT_MAX/4);  //INT_MAX是整型最大值
	*p = 20;    //如果p的值是NULL,就会有问题
	free(p);    //应对动态内存开辟后的返回值作判断。
}

3.2 对动态开辟空间的越界访问
void test()
{
	int i = 0;
	int *p = (int *)malloc(10*sizeof(int));
	
	if(NULL == p)
	{
        printf("%sn", strerror(errno));
		return 1;
	}
	
	for(i=0; i<=10; i++)
	{
		*(p+i) = i;//当i是10的时候越界访问
	}
	free(p);
    p = NULL;
}

3.3 对非动态开辟内存使用free释放
void test()
{
	int a = 10;
	int *p = &a;
	free(p);     //非动态开辟的空间不能使用free
}

3.4 使用free释放一块动态开辟内存的一部分or忘记释放
void test()
{
	int *p = (int *)malloc(40);
    if(NULL == p)
	{
		return 1;
	}
    for(i=0; i<10; i++)
	{
		*p = i;
        p++;
	}
	free(p);//p不再指向动态内存的起始位置
}

3.5 对同一块动态内存多次释放
void test()
{
	int *p = (int *)malloc(100);
	free(p);
	free(p);//重复释放
}

4 几个经典的面试题

4.1 题目1
void GetMemory(char *p)
{
	p = (char *)malloc(100);
}

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

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

 为避免这种错误,可以:

  • //在传参的时候传&str,然后形参部分用二级指针接收即可
    void GetMemory(char **p)
    {
    	*p = (char *)malloc(100);
    }
    
    void Test(void)
    {
    	char *str = NULL;
    	GetMemory(&str);   //str存放的是动态开辟的100字节的地址
    	strcpy(str, "hello world");
    	printf(str);
        free(str);
        str = NULL;
    }
    
  • //更改返回值
    char* GetMemory()
    {
    	char *p = (char *)malloc(100);
        return p;
    }
    
    void Test(void)
    {
    	char *str = NULL;
    	str = GetMemory();
    	strcpy(str, "hello world");
    	printf(str);
        free(str);
        str = NULL;
    }
    

4.2 题目2
char *GetMemory(void)
{
	char p[] = "hello world";
	return p;
}

void Test(void)
{
	char *str = NULL;
	str = GetMemory();
	printf(str);
}

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

        以上程序有如下错误:

  • GetMemory函数内部创建的数组是在栈区上创建的出了函数,结束函数时,p数组的空间就还给操作系统,返回的地址是没有意义的,str就是一个野指针。如果通过返回的地址去访问内存,就是非法访问内存。

4.3 题目3
void Test(void)
{
	char *str = (char *) malloc(100);
	strcpy(str, "hello");
	free(str);
	if(str != NULL)
	{
		strcpy(str, "world");
		printf(str);
	}
}

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

        以上程序有如下错误:

  • 内存被提前释放,造成非法访问内存的情况

        解决方法:释放完内存后,对指针认为置为NULL,这样就访问不到了

5 柔性数组(暂略) 暑期编程PK赛 得CSDN机械键盘等精美礼品!

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/1016825.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号