IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> [c语言]动态内存分配|malloc realloc calloc函数|相关错误|习题|柔性数组 -> 正文阅读

[C++知识库][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;
	}
  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2022-01-28 11:42:41  更:2022-01-28 11:43:13 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 10:00:17-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码