「C语言进阶1」动态内存分配

本文详细介绍了动态内存分配的概念、原因、C语言中malloc、calloc、realloc和free的使用方法,以及动态内存分配中可能出现的问题和面试题示例。
摘要由CSDN通过智能技术生成

目录

一、动态内存分配是什么?

二、为什么需要动态内存分配?

三、怎么进行动态内存分配?

1. malloc

2. calloc

3. realloc

a. realloc功能解析

b. 内存泄漏和内存块被截断问题

c. 总结

4. free 

四、使用动态内存分配常见的问题

【面试题】


一、动态内存分配是什么?

        动态内存分配是指在程序执行的过程中,系统根据程序的需要动态地分配或回收存储空间的分配内存的方法。

        这种分配方式不像数组等静态内存分配方法那样,需要预先指定要分配的内存大小,且一经分配后大小无法修改,而是由系统按程序的需要灵活地分配和回收内存空间。


二、为什么需要动态内存分配

        在C99引入变长数组前,定义数组时大小不能是变量,这就导致了开辟数组时必须指定数组的大小,且一经开辟,数组的大小再也无法改变。但实际情况下,如实现一个通讯录时,我们往往不知道需要多大的数组,且经常会遇到数组容量不够的情况,这就是静态内存分配的弊端。

        早期的解决方案就是使用动态内存分配,也就是使用四个库函数:malloc、calloc、 realloc、free对堆区的内存进行更灵活的分配和回收。

使用动态内存分配的优势:

  1. 可以灵活分配、回收内存:
            与大小固定的数组相比,动态内存分配可以使用malloc、calloc申请分配内存,使用relloc对申请好的内存空间大小进行调整,使用free回收内存。
  2. 可以随时回收,多次利用这部分空间:
            静态内存分配的空间,直到整个程序结束才会由系统自动释放。但是动态内存分配的空间需要用户手动释放,所以我们可以在使用完后立马通过free函数空间,使得这块空间能在一个程序中被反复使用,这样也能在一定程度上节省内存空间。
  3. 使用内存中堆区的空间
            动态内存分配是在堆上分配空间的,静态内存分配是在栈上分配空间的,所以了解动态内存分配后就能使用堆上的空间了。

三、怎么进行动态内存分配?

        C语言中动态内存管理方式是通过四个库函数实现的:malloc、calloc、 realloc、free。这四个库函数都在头文件<stdlib.h>

1. malloc

函数声明    |        void*  malloc (size_t  size);
功能      |        在堆上申请一个大小为size字节的空间。(申请分配动态内存)
返回值     |        若申请成功,返回一个指向该空间开头的指针;若失败,则返回空指针。
#include<stdio.h>
#include<stdlib.h>

int main()
{
	// 开辟一个动态内存变量
    //malloc函数的返回值类型为void*所以要强制类型转换
	int* a = (int*)malloc(1 * sizeof(int));
	// 检查动态内存是否开辟失败,开辟失败返回NULL
	if (NULL == a)
	{
		printf("动态内存开辟失败。\n");
		return 0;
	}
	// 使用动态内存
	*a = 1;
	printf("%d\n", *a);
	// 释放动态内存
	free(a);
    a = NULL; //free后指针仍指向原来的空间,所以要置为NULL
}
	// 开辟一个动态内存数组
	int size = 10; // 动态内存数组大小
	int* arr = (int*)malloc(size * sizeof(int));
	// 检查动态内存是否开辟失败
	if (NULL == arr)
	{
		printf("动态内存开辟失败。\n");
		return 0;
	}
	// 使用动态内存数组(和静态内存数组的使用没有差别)
	for (int i = 0; i < size; ++i)
	{
		arr[i] = i;
		printf("%d ", arr[i]);
	}
	// 释放动态内存数组
	free(arr);
    arr = NULL;

2. calloc

函数声明    |        void* calloc (size_t  num, size_t  size);
功能      |        在堆上申请大小为num个size字节的空间,并初始化空间中的内容为0。(分配动态内存)
返回值     |        若申请成功,返回一个指向该空间开头的指针;若失败,则返回空指针。
	// 使用calloc函数开辟一个动态内存变量/数组
	int size = 10; // 动态内存数组大小,size = 1即为变量
	int* arrc = (int*)calloc(size , sizeof(int));// calloc函数的返回值类型为void*
	if (NULL == arrc)// 检查
	{
		printf("动态内存开辟失败。\n");
		return 0;
	}
	for (int i = 0; i < size; ++i)// 使用
	{
		printf("%d ", arrc[i]); //calloc默认初始化内容全为0
	}
	free(arrc);// 释放
    arrc = NULL;

3. realloc

函数声明    |        void* realloc (void*  mem_address, unsigned int  newsize);
功能      |        见下方解析。(对申请好的动态内存空间大小进行调整,可以扩展或缩小动态内存。
返回值     |        若申请成功,返回一个指向该空间开头的指针;若失败,则返回空指针。

a. realloc功能解析

        realloc函数用于对申请好的动态内存空间大小进行调整。它接受两个参数:一个是已分配的动态内存的指针,另一个是要重新分配的大小(单位字节)。

        realloc函数首先检查传入的指针是否为空指针。如果是空指针,则等同于调用malloc函数,直接分配指定大小的动态内存,并返回指向该动态内存的指针。
        如果传入的指针不是空指针,则realloc函数会尝试重新分配内存块的大小。它会根据传入的新内存块大小newsize和原有内存块的大小oldsize来判断如何重新分配内存:

  • newsize = 0,释放原有的内存块,并返回空指针。
  • newsize > oldsize,则realloc函数会尝试在指针参数指向原有内存块的上进行扩容:
    如果原有内存块有足够的连续空间,直接扩容,并且将传入的指针返回;
    如果没有足够的连续空间,先按照指定的大小分配一个新的内存块,然后将原有数据拷贝到新内存块中,而后由系统自动释放原来的内存块,并返回新内存块起始位置的指针;
    如果扩容失败,则返回空指针,并且原有内存块的内容保持不变。
  • newsize < oldsize,则realloc函数会尝试缩小内存块的大小:
    它可能会直接使用缩小后的原内存块;
    也可能将原内存块中的数据复制到新内存块中,并返回指向新内存块的指针。内容不变,但是多余的数据可能会被截断。

b. 内存泄漏和内存块被截断问题

使用realloc函数时需要小心内存泄漏和内存块被截断的问题:

  • 如果realloc函数返回空指针,则说明内存分配失败,原内存块没有被释放,这可能会导致内存泄漏;(realloc失败不会释放原空间)
  • realloc函数可能会因为原来位置的内存大小不够,将原有内存块移到新的位置。但是,指向原有内存块的指针仍然指向原内存块,这可能会导致内存泄漏。所以realloc后要将原指针置为NULL。
  • 如果新的内存块大小小于原有内存块的大小,则可能会导致多余的数据被截断。

c. 总结

        realloc函数用于重新分配内存块的大小,可以扩展或缩小内存块。但是使用时需要注意内存泄漏和数据截断的问题。

	// 使用realloc函数调整动态内存的大小
	// 1.如果传入的是空指针,则相当于使用malloc开辟新空间:
	int* p1 = (int*)realloc(NULL, 1 * sizeof(int));
	if (NULL == p1)
	{
		printf("动态内存开辟失败。\n");
		return 0;
	}
	*p1 = 1;
	printf("%d\n", *p1);
	free(p1);
		printf("p1所指向的空间:%d\n", sizeof p1);
	// 2.如果新空间需要的大小newsize = 0
	//则释放原有的内存块,并返回空指针。
	int* p2 = (int*)malloc(1 * sizeof(int));
	int* p3 = (int*)realloc(p2, 0);
	if (NULL == p3)
		printf("p2, p3所指向的空间将会由系统在合适的时间释放。\n");

	// 3.指针指向的旧空间大小oldsize < 新空间需要的大小newsize
	// 可能在原空间上扩容,也可能找一个新空间扩容
	int* p4 = (int*)calloc(10, sizeof(int));
	int* p5 = (int*)realloc(p4, 12 * sizeof(int));
	int* p6 = (int*)realloc(p5, 1000 * sizeof(int));
	if (p4 == p5)
		printf("在原空间上扩容。\n");
	if (p5 != p6)
		printf("找一个新空间扩容。\n");

	// 4.oldsize > newsize
	// 可能使用原空间,也可能找一个新空间
	int* p7 = (int*)calloc(10, sizeof(int));
	int* p8 = (int*)realloc(p7, 9 * sizeof(int));
	int* p9 = (int*)realloc(p8, 2 * sizeof(int));
	if (p7 == p8)
		printf("使用原空间。\n");
	if (p8 != p9)
		printf("找一个新空间。\n");

4. free 

函数声明    |        void free (void*  ptr);
        |        释放ptr指向的空间,如果ptr是空指针则不做处理;如果ptr指向的空间不是动态内存或是
功能      |        已经被释放的动态内存,则free函数的结果是未知的——可能导致程序崩溃,也可能导致
        |        内存泄漏。(回收动态内存)
返回值     |        无。

        释放动态内存,并不意味着执行到free函数时立马让该空间的内存全变为0,也不会让指针指向NULL,这只是告诉操作系统可以重新使用该内存块,具体的释放时间由操作系统决定。所以free后要手动将指针置为NULL。这也意味着无法通过指针是否指向NULL来判断空间是否已经被释放。

        注意,只有动态申请的堆区内存需要我们主动释放,其它区都由系统管理,强行free可能导致程序崩溃:


四、使用动态内存分配常见的问题

3.1 不检查动态内存是否开辟成功

如果开辟失败,返回值为NULL,不检查就可能会对NULL进行解引用操作。
    int *p = (int *)malloc(INT_MAX/4);
    *p = 20;//如果p的值是NULL,就会有问题
    free(p);

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

和静态数组一样,因忘记数组大小,导致越界访问。
	int* p = (int*)malloc(10 * sizeof(int));
	if (NULL == p)
	{
		exit(EXIT_FAILURE);
	}
	for (int i = 0; i <= 10; i++)
	{
		*(p + i) = i;//当i是10的时候越界访问
	}
	free(p);

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

强行free非动态内存可能导致程序崩溃
	int t = 1;
	int* tp = &t;
	free(tp);
	return 0;

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

指向动态内存的指针不再指向起始位置。
	int* p = (int*)malloc(100);
	p++;
	free(p);//p不再指向动态内存的起始位置

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

一样可能导致程序崩溃。
	int* p = (int*)calloc(10, sizeof(int));
	if (NULL == p)
	{
		exit(EXIT_FAILURE);
	}
	free(p);
	free(p);
	return 0;

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

        最常见的问题:忘记使用free函数释放动态内存空间,如果是一个长期运行的程序,这部分内存空间会因未释放而一直被占用,这就导致程序消耗的内存越来越多。切记: 动态开辟的空间一定要释放,并且正确释放 。

3.7 使用realloc和free后,未将原指针置为NULL

        realloc函数可能原位置空间不足,而使用新内存块移到新的位置。但是不会让指向原有内存块的指针不会指向NULL。free函数也不会让指针指向NULL。这可能会导致内存泄漏。所以realloc和free后要将原指针置为NULL。


【面试题】

题目1

void Test()
{
    int* p1 = (int*)malloc(sizeof(int));
    free(p1);
    // 1.malloc/calloc/realloc的区别是什么?
    int* p2 = (int*)calloc(4, sizeof(int));
    int* p3 = (int*)realloc(p2, sizeof(int) * 10);
    // 2.这里需要free(p2)吗?
    free(p3);
}


1. malloc/calloc/realloc的区别?

        从参数和函数功能方面进行回答。


2.这里需要free(p2)吗?

        答:不需要,使用realloc扩容时,自动释放了p2之前的空间。

题目2

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


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

        答:运行Test函数会导致程序崩溃,因为形参只是实参的拷贝,形参的改变并不影响实参,所以str仍指向NULL,这会导致空指针异常。

题目3 

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


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

        答:不确定,因为在函数GetMemory执行完后,所处空间被系统销毁,p指向的空间也被销毁了,此时str因函数返回值指向了p指向的这片被消毁的空间。所以运行Test函数会产生不确定的结果。

题目4:

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


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

        答: 可能会导致程序崩溃,也可能会在显示屏上打印world。因为不是执行free函数就立即销毁指针指向的空间,指针也不会被free函数被置为NULL,所以指针仍指向原空间,如果系统还没销毁该空间,那么会在显示屏上打印world,否则会导致程序崩溃。


------------------------END-------------------------

才疏学浅,谬误难免,欢迎各位批评指正。

  • 23
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

烛火萤辉

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值