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++知识库]动态内存管理

? ? ? ?

? ? ? ?我们都知道平常的局部变量和函数形式参数实在栈区开辟的,但这个时候其内存所开辟空间的大小是固定的,而且所需要的内存在编译时分配,但很多时候我们所需要的空间大小在程序运行的时候才能知道,这个时候我们就只能试试动态内存开辟了。

目录

1.动态内存函数的介绍

1.1 malloc和free

1.2 calloc函数

1.3? realloc函数

2.常见的动态内存错误

2.1对NULL指针的解引用操作

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

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

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

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

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

3.动态内存经典笔试题

题1:

题2:

题3:

题4:

4.C/C++程序的内存开辟

5.柔性数组

5.1柔性数组概念

5.2柔性数组的特点

5.3柔性数组的优势


1.动态内存函数的介绍

1.1 mallocfree

void* malloc (size_t size);

malloc函数的使用方法

这个函数向内存申请一块连续可用的空间,并返回指向这块空间的指针。

如果开辟成功,则返回一个指向开辟好空间的指针。

如果开辟失败,则返回一个NULL指针,因此malloc的返回值一定要做检查。

返回值的类型是void*,所以malloc函数并不知道开辟空间的类型,具体在使用的时候使用者自己来决定。

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

C语言提供了另外一个函数free,专门是用来做动态内存的释放和回收

#include <stdlib.h>
int main()
{
	int arr[10] = { 0 };
	int* p = (int *)malloc(40);//申请空间
	if (p == NULL)
	{
		return -1;
	}
	//开辟成功了
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		*(p + i) = i;
	}
     free(p);
     p=NULL;
	return 0;//p必须置为空,否则变成危险的野指针
}
 free(p);

1.2 calloc函数

void* calloc (size_t num, size_t size);

函数的功能是为num个大小为size的元素开辟一块空间,并且把空间的每个字节初始化为0。与函

数malloc的区别只在于calloc会在返回地址之前把申请的空间的每个字节初始化为全0。

#include <stdio.h>
#include <stdlib.h>
int main()
{ 
    int *p = (int*)calloc(10, sizeof(int)); 
    if(NULL != p)
   { 
    //使用空间
   }
      free(p);
      p=NULL;
    return 0;
}

1.3? realloc函数

? ? ? ?realloc函数的出现让动态内存管理更加灵活。有时会我们发现过去申请的空间太小了,有时候我们又会觉得申请的空间过大了,那为了合理的时候内存,我们一定会对内存的大小做灵活的调整。那realloc函数就可以做到对动态开辟内存大小的调整。

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

ptr是要调整的内存地址

size 调整之后新大小

返回值为调整之后的内存起始位置。

这个函数调整原内存空间大小的基础上,还会将原来内存中的数据移动到新的空间。

int main()
{
      //申请10个int的空间
      int*p = (int*)calloc(10, sizeof(int));
      if (p == NULL)
     {
         printf("%s\n", strerror(errno));
         return -1;
     }
       //申请成功
      int i = 0;
       for (i = 0; i < 10; i++)
     {
        *(p + i) = i;
     }
   //空间不够了,增加空间至20 个int
     int*ptr = (int*)realloc(p, 20*sizeof(int));
    if (ptr != NULL)
    {
        p = ptr;//如果开辟成功,将ptr赋值给p
    }
   else
    {
        return -1;
    }
     for (i = 10; i < 20; i++)
    {
         *(p + i) = i;
    }
     //打印
    for (i = 0; i < 20; i++)
    {
        printf("%d ", *(p + i));
    }
    //释放空间
    free(p);
    p = NULL;
    return 0;
}

realloc在调整内存空间的是存在两种情况:

第一种:原有空间之后有足够大的空间,此时要扩展内存就直接原有内存之后直接追加空间,原来空间的数据不发生变化。

第二种:原有空间之后没有足够大的空间,在堆空间上另找一个合适大小的连续空间来使用。这样函数返回的是一个新的内存地址。

2.常见的动态内存错误

2.1对NULL指针的解引用操作

void test()
{ 
  int *p = (int *)malloc(20); 
  *p = 20;//如果p的值是NULL,解引用就会有问题 
   free(p);
}

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

int main()
{
      int* p = (int*)malloc(200);
     if (p == NULL)
    {
       return -1;
     }
     //使用
     int i = 0;
     //越界访问
       for (i = 0; i < 80; i++)//因为此时只开辟200字节,最多放50个元素,访问80个已经越界
     {
       *(p + i) = i;
     }
      for (i = 0; i < 80; i++)
     {
         printf("%d\n", *(p + i));
     }
    //释放
    free(p);
    p = NULL;
    return 0;
}

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

void test()
{ 
  int a = 10; 
  int *p = &a; 
  free(p);
}

此时free函数的行为是未定义的

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

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

此时p指针指向空间最后的地址,因此free函数不能完全释放

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

void test()
{ 
   int *p = (int *)malloc(100); 
   free(p); 
   free(p);//重复释放
}

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

int main()
{
     int *p = (int*)malloc(40);
     if (p == NULL)
     return -1;
        //使用
       //忘记释放了
     getchar();//
     return 0;
}

3.动态内存经典笔试题

题1:

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

此时的程序运行会有什么问题呢?

? ? ? ?首先我们建立一个char*类型的空指针,然后我们调用GetMemory函数,str是值传递,用p来接收,因此p是str的一份临时拷贝,所以当malloc开辟的空间起始地址放在p中的时候,不会影响str,str仍然是NULL指针。当str是空指针的时候,strcpy是不能直接将字符串直接拷贝到str空间的,因为NULL指向的空间是不能直接被访问的,因此程序会崩溃。

既然如此,我们应该如何改正呢?
我们可以将值传递改为址传递,或者强行将malloc开辟的空间带回来

下面我们看正确代码:

方法1:传址操作

void GetMenory(char** p)//一级指针str的地址用二级指针p接收
{
	*p = (char *)malloc(100);//*p表示str
}
void Test(void)
{
	char *str = NULL;
	GetMemory(&str);
	strcpy(str, "hello world");
	printf(str);
	free(str);
	str = NULL;
}
int main()
{
	Test();
    return 0;
}

方法2:强行带回p

void GetMenory(char* p)
{
	p = (char *)malloc(100);
    return p;
}
void Test(void)
{
	char *str = NULL;
	str=GetMemory(str);
	strcpy(str, "hello world");
	printf(str);
	free(str);
	str = NULL;
}
int main()
{
	Test();
    return 0;
}

题2:

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

?此题打印的是随机值:

? ? ? ?主要是因为我们调用GetMemory函数的时候,在里面创建了字符数组,再带回来时,创建的数组已经销毁,因此str打印出来的是随机值。创建数组的时候前面加static静态变量就可以。

题3:

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

此题是传址操作,前面都没问题,可是strcpy将字符串复制之后,并没有释放malloc开辟的空间,因此会造成内存泄漏。后面加上free(str);str=NULL;即正确。

题4:

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

此处char* str接收malloc开辟的空间,后面使用完之后,并没有将str置为空指针,就直接判断str是否为空,此时这里的str是个野指针,因此free(str)之后str=NULL,则正确

4.C/C++程序的内存开辟

?

?C/C++程序内存分配的几个区域:

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

2. 堆区(heap):一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收 。分配方式类似于链表。

3. 数据段(静态区)(static)存放全局变量、静态数据。程序结束后由系统释放。

4. 代码段:存放函数体(类成员函数和全局函数)的二进制代码。

5.柔性数组

5.1柔性数组概念

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

?

typedef struct st_type
{ 
   int i; 
   int a[0];//柔性数组成员
}type_a;

5.2柔性数组的特点

结构中的柔性数组成员前面必须至少一个其他成员。

sizeof 返回的这种结构大小不包括柔性数组的内存。

包含柔性数组成员的结构用malloc ()函数进行内存的动态分配,并且分配的内存应该大于结构的大小,以适应柔性数组的预期大小。

即柔性数组一般与动态开辟内存配合使用:

举例:

#include <errno.h>.
struct st_type
{
	int i;
	int a[];
};
int main()
{
	struct st_type* ps = (struct st_type*)malloc(sizeof(struct st_type) + 10 * sizeof(int));
	if (ps == NULL)
	{
		printf("%s\n", strerror(errno));
		return -1;
	}
	//开辟成功
	ps->i = 100;
	int i = 0;
	for (i = 0; i < 10;i++)
	{
		ps->a[i] = i;
	}
	for (i = 0; i < 10; i++)
	{
		printf("%d ", ps->a[i]);
	}
	//a数组空间内容不够了,希望调整为20个整形数据
	struct st_type* ptr = (struct st_type*)realloc(ps, sizeof(struct st_type) + 20 * sizeof(int));
	if (ptr == NULL)
	{
		printf("扩展空间失败\n");
		return -1;
	}
	else
	{
		ps = ptr;
	}
	//使用
	//释放
	free(ps);
	ps = NULL;

	return 0;
}

5.3柔性数组的优势

上个柔性数组也有另一种使用方式:

typedef struct st_type
{
	int i;
	int *p_a;
}type_a; 
type_a* p = (type_a *)malloc(sizeof(type_a)); 
p->i = 100; 
p->p_a = (int *)malloc(p->i*sizeof(int));
for(i=0; i<100; i++)
{ 
	p->p_a[i] = i;
}
//释放空间
free(p->p_a);
p->p_a = NULL;
free(p);
p = NULL;

但此处free释放malloc开辟的空间要释放两次,柔性数组只需要释放一次。

使用柔性数组好处1:方便内存释放

? ? ? ? ? ? ? ? ? ? ? ? ? ? ?2:连续的内存有益于提高访问速度,也有益于减少内存碎片。

  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2021-10-03 16:54:12  更:2021-10-03 16:54:21 
 
开发: 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年12日历 -2024/12/29 4:21:15-

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