[c语言]动态内存分配|malloc realloc calloc函数|相关错误|习题|柔性数组

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

我们一般的内存开辟方式:

int val = 10;//在栈空间上开辟4个字节
char arr[20] = { 0 };//在栈空间上开辟20个字节

这种方式开辟的空间大小是固定的,它所需要的空间在编译时分配。

但在实际需求中,有时我们的需要的空间大小在程序运行时才知道。

为了能自由调整空间大小,我们就需要动态内存开辟了。

相关函数介绍

mallocfree

头文件stdlib.h

常用的开辟空间的函数malloc

void* malloc (size_t size);
  • 开辟一个大小为size字节的内存块,返回指向该内存块开头的指针。
    • 如果开辟失败,则返回NULL,因此malloc的返回值一定要做检查。(如不检查,vs会报出警告:取消对 NULL 指针“p”的引用。)
  • 新分配的内存块的内容未初始化,里面的值不确定。
  • 如果size为0,则返回值取决于编译器(可能是NULL也可能不是NULL)。

与之相应的释放动态内存函数free

void free (void* ptr);
  • 释放通过调用malloccallocrealloc分配的内存块。
  • 如果ptr不指向使用上述函数分配的内存块,则会导致未定义的行为。
  • 如果==ptr是空指针==,则函数不执行任何操作。
  • 注意:此函数==不会更改ptr==本身的值,因此它仍然指向原来的位置(但是不能再使用)。

例子🌰

int main()
{
	//开辟
	int* p = (int*)malloc(40);//因为返回的是void*,所以可以根据需要进行转换,以便解引用
	if (p == NULL)//检查
	{
		printf("%s", strerror(errno));
		return 0;
	}
	//使用

	//释放
	free(p);
	p = NULL;//是否有必要?
	return 0;
}

p=NULL是否有必要?

由于free函数不会改变指针的值,因此在把它所指向的空间释放后,该指针成为野指针,安全起见,应该置为空。

calloc

void* calloc (size_t num, size_t size);
  • 为num个size字节大小的元素分配一块内存,并将其所有bit位初始化为0。
  • 如果size为0,则返回值取决于编译器(可能是NULL也可能不是NULL)。

功能上与malloc的区别就在于会把开辟的空间初始化

realloc

void* realloc (void* ptr, size_t size);
  • 调整之前申请的空间的大小。
  • ptr指向空间的大小调整为size,返回调整后的空间的起始地址。
  • 如果==ptr是空指针==,则类似于malloc,分配一个size字节的空间,返回指向其起始位置的指针。
  • 如果==size为0==,ptr指向的空间将被释放,效果类似free一样,并返回空指针(c99返回值取决于编译器)。
  • 如果开辟失败,则返回一个空指针,并且ptr指向的内存块不会被释放,且内容不变。
  • 如果缩小空间,则在原空间的基础上减去高地址的空间,返回的仍是原空间的地址。
  • realloc在扩大空间时有两种情况:

    • 情况1:原空间后有足够的空间

      这种情况下,则直接在原空间后追加空间,追加部分空间未初始化

    • 情况2:原空间后没有足够的空间

      这种情况下,函数会在堆区另外找一块合适的空间,并将原空间的数据移到新空间,这样返回的就是新空间的地址。

例子🌰

int main()
{
	//开辟
	int* p = (int*)malloc(40);
	if (p == NULL)
	{
		printf("%s", strerror(errno));
		return 0;
	}
	//使用

	//需要增容
	int* ptr = (int*)realloc(p, 80);//①
	if (ptr != NULL)
	{
		p = ptr;
		ptr = NULL;
	}

	//释放
	free(p);
	p = NULL;
	return 0;
}

注意:①处不可以直接写为p = (int*)realloc(p, 80);因为一旦开辟失败,p被改为NULL,那么连原来的空间都找不到了。

常见错误总结

对空指针的解引用

例子🌰

int main()
{
	int* p = (int*)malloc(INT_MAX / 4);
	*p = 20;//如果p的值是NULL,就会有问题
	free(p);
    p = NULL;
	return 0;
}

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

例子🌰

int main()
{
	int* p = (int*)malloc(10 * sizeof(int));
	if (p == NULL)
	{
		printf("%s", strerror(errno));
		return 0;
	}
	for (int i = 0; i <= 10; i++)//越界访问
	{
		*(p + i) = i;
	}
	free(p);
	p = NULL;
	return 0;
}

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

例子🌰

int main()
{
	int a = 0;
	int* p = &a;
	free(p);//×错误
	return 0;
}

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

例子🌰

int main()
{
	int* p = (int*)malloc(4 * sizeof(int));
	for (int i = 0; i < 2; i++)
	{
		*p = i;
		p++;
	}
	free(p);//此时p不指向空间起始位置
	return 0;
}

对同一块空间多次释放

int main()
{
	int* p = (int*)malloc(4 * sizeof(int));
	free(p);
	free(p);
	return 0;
}

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

void test()
{
	int* p = (int*)malloc(4 * sizeof(int));
	if (p == NULL)
		return 0;
	//使用...

	//忘记释放
}
int main()
{
	test();
	return 0;
}

这里p为局部变量,如果在函数内部忘记释放,走出函数后p被销毁,其所指向的动态开辟内存再也找不到,并且无法释放,这块内存将一直被占用,无法再次开辟使用,造成内存泄漏

虽然程序运行结束会自动释放内存,但对于长期运行的程序,内存泄漏会导致内存越用越小,最终造成严重的后果。

注意:动态开辟的空间一定要记得释放,并且正确释放

相关题目

❓题目1

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

这段代码最终结果是什么?

答案:程序崩溃

因为函数采用传值调用的方式,动态开辟的空间并没有给到str,str依然为NULL,不符合strcpy的规则。

正确的方式:

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

❓题目2

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

这段代码最终结果是什么?

答案:随机值

p数组为局部变量,虽然其地址被成功返回了,但由于函数外这块栈区空间被回收,str成为野指针,再通过地址去访问它得到的只能是随机值。

学完了动态内存分配,我们可以写一个动态版本的通讯录。[C语言] 通讯录|静态 动态 文件 链表 多版本讲解_CegghnnoR的博客-CSDN博客


柔性数组

C99 中,结构中的最后一个成员允许是未知大小的数组,这就叫做『柔性数组』成员。

struct s1
{
	int i;
	int a[0];//柔性数组成员,a[0]也可写成a[]
};

  • 结构中的柔性数组成员前面必须至少一个其他成员。
  • sizeof 返回的这种结构大小不包括柔性数组的内存。
  • 包含柔性数组成员的结构用malloc ()函数进行内存的动态分配,并且分配的内存应该大于结构的大小,以适应柔性数组的预期大小。

例子🌰

struct s1
{
	int i;
	int a[0];
}; 
int main()
{
	//printf("%d\n", sizeof(struct s1));//该结构体大小为多少?
	struct s1* p = (struct s1*)malloc(sizeof(struct s1) + 40);
	if (p == NULL)
		return 0;
	//使用
	p->i = 10;
	for (int i = 0; i < 10; i++)
	{
		p->a[i] = i;
	}
	//释放
	free(p);
	p = NULL;
	return 0;
}

sizeof返回4,因为不包括柔性数组内存。使用malloc分配内存时,sizeof(struct s1)表示分配给int i的大小,40才是分配给柔性数组的大小。

后续也可以使用realloc对柔性数组的大小进行修改:

	//a数组增容到80字节
	struct s1* ptr = (struct s1*)realloc(p, sizeof(struct s1) + 80);
	if (ptr != NULL)
	{
		p = ptr;
		ptr = NULL;
	}
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

世真

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

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

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

打赏作者

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

抵扣说明:

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

余额充值