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语言动态内存管理 -> 正文阅读

[C++知识库]C语言动态内存管理

C语言动态内存管理

前言 -----------> 进程地址空间

我们看下面的图,进程地址空间大致可以分为三大类:栈区(局部变量,函数等),堆区(动态开辟的空间),静态区(常量字符串,static修饰的静态变量),下面我们所讲的是在堆区上动态开辟的空间

image-20220305202026469

image-20220305211121071

1、为什么要动态开辟空间

我们已经掌握的内存开辟方式有:

int val = 20;//在栈空间上开辟四个字节

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

上述开辟的空间有两个特点:

  1. 空间开辟大小固定
  2. 数组在定义的时候,必须给定数组大小,它所需要的内存在编译时给分配了

但是对于空间的需求,不仅仅是上述的情况。有时候我们需要的空间大小在程序运行的时候才能知道,那数组的编译时开辟空间的方式就不能满足了。

这时候就只能试试动态存开辟

2、动态内存函数介绍

2.1 malloc与free

内存开辟malloc:

  • void* malloc (size_t size);

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

注意:malloc开辟的空间不会进行初始化

  • 如果开辟成功,则返回一个指向开辟好空间的指针。
  • 如果开辟失败,则返回一个NULL指针,因此malloc的返回值一定要做检查。
  • 返回值的类型是 void* ,所以malloc函数并不知道开辟空间的类型,具体在使用的时候使用者自己来决定。(利用强制类型转换来实现)
  • 是否支持申请0字节空间,由编译器决定

内存释放free

  • void free (void* ptr);

free函数用来释放动态开辟的内存(还给操作系统)。

  • 如果参数 ptr 是NULL指针,则函数什么事都不做。
  • 如果参数非动态开辟的,程序就会报错

代码展示

#define num 10

int main()
{
	int* ptr = NULL;
	ptr = (int*)malloc(num*sizeof(int));
	if (NULL != ptr)//判断ptr指针是否为空
	{
		int i = 0;
		for (i = 0; i<num; i++)
		{
			*(ptr + i) = 0;
		}
        
        
	}
	free(ptr);//释放ptr所指向的动态内存
	ptr = NULL;// 为了防止free多次释放(若没有置空,多次释放会报错)

	return 0;
}

image-20220305205001066

2.2 calloc

calloc 函数也用来动态内存分配

  • void* calloc (size_t num, size_t size);
  • 函数的功能是为 num 个大小为 size 的元素开辟一块空间,并且把空间的每个字节初始化为0。

代码展示

int main()
{
	int *p = (int*)calloc(10, sizeof(int));
	if (NULL != p)
	{
		//使用空间
	}
	free(p);
	p = NULL;
	return 0;
}

image-20220305210526599

2.3 realloc

realloc让动态内存更加的灵活,它是用来调整开辟后的大小的(若前面开辟的空间不够,就可以用realloc对动态内存扩容)

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

  • ptr 是要调整的内存地址

  • size 调整之后新大小 — 单位:字节

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

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

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

    image-20220305210404977

代码展示

int main()
{
	int* p = (int*)calloc(10, sizeof(int));
	if (p == NULL)
	{
		printf("%s\n", strerror(errno));
		exit(-1);
	}
	else
	{
		//放入数据
		int i = 0;
		for (i = 0; i<10; i++)
		{
			*(p + i) = i;
		}
	}
	//上面就是在使用calloc开辟的40个字节的空间
	//假设后面还需要使用空间,而上面存了0-9共10个整形数,已经满了,就不够,希望将开辟的空间扩大
	//这里realloc就可以来调整空间大小
	int* ptr = (int*)realloc(p, 80);//把原来40字节的空间变成80
	if (ptr != NULL)
	{
		p = ptr;
		//放入数据
		int i = 0;
		for (i = 10; i<20; i++)
		{
			*(p + i) = i;
		}

		for (i = 0; i < 20; i++)
		{
			printf("%d ", *(p + i));
		}
		printf("\n");
	}
	free(p);
	p = NULL;
}

image-20220305211008242

3、常见的动态内存错误

3.1 对NULL进行解引用操作

void test()
{
 int *p = (int *)malloc(INT_MAX/4);
 *p = 20;//如果p的值是NULL,就会有问题,所以每次使用动态开辟的内存需要判断是否为NULL
 free(p);
}

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

//2.越界访问
int main()
{
    int* p = malloc(5*sizeof(int));
    if(p == NULL)
    {
        return 0;
    }
    else
    {
        int i = 0;
        //只开辟了5个整形空间,但访问了超过5个
        for(i=0;i<10;i++)
        {
            *(p+i) = i;
        }
    }
    free(p);
    p = NULL;
    return 0;
}

3.3 对非动态开辟的空间进行free释放

//3.对非动态内存空间使用free
int main()
{
    int a = 10;
    int p* = &a;
    *p = 20;
    
    free(p);
    p = NULL;
    return 0;
}

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

//4.使用free释放动态内存的一部分
int main()
{
    int* p =(int*)malloc(40);
    if(p==NULL)
    {
        return 0;
    }
    int i = 0;
    for(i=0;i<10;i++)
    {
        // *(p+i) = i;  // 可以这样写的
        *p++ = i;//p被改变了,它指向的不是开辟空间的开头位置
    }
    free(p);
    p = NULL;
    return 0;;
}

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

//5.多次释放同一块动态内存
int main()
{
    int* p =(int*)malloc(40);
    if(p==NULL)
    {
        return 0;
    }
    int i = 0;
    for(i=0;i<10;i++)
    {
        *(p+i) = i;
    }
    free(p);
    p = NULL;//若没有释放后将p置为NULL,后面在进行释放的使用,就会出问题,所以推荐置空
    
    
    return 0;;
}

3.6 忘记释放动态内存开辟的空间

//6忘记释放,内存泄漏(内存被用完)
int main()
{
    while(1)
    {
        malloc(1);
        Sleep(1000);
    }
    return 0;
}

4. 经典笔试题

1.

//1.str -> p ->开辟的空间,p被回收,空间就找不到了
// 改正方法:1. 通过返回值,调用函数的一方进行释放;2. 实参传递的时候采用传址调用
void Getmemony(char* p)
{
    p = (char*)malloc(100);
}//内存泄漏,这个函数执行完后,p变量就被回收了,开辟的内存就没有变量能找到了(野指针)

int main()
{
    char* str = NULL;
    Getmemory(str);
    strcpy(str,"abcdef");//程序崩溃,str = null
    printf(str);
}

2.

//2.str -> p ->开辟的空间,p数组被回收,p所指向的空间就被回收了(p是栈区上开辟的,第一题的p是malloc在堆上开辟的)
char* Getmemony(void)
{
    //static p[] = "hello world";静态区就可以
    p[] = "hello world";//栈区
    return p;
}

int main()
{
    char* str = NULL;
    str = Getmemory();//非法访问(函数返回的空间已不再是改程序的了)
    printf(str);
}

3.

// 3
void Getmemony(char** p,int num)
{
    *p = (char*)malloc(num);
}

int main()
{
    char* str = NULL;
    Getmemory(&str,100);
    //没有free,存在内存泄漏,,而且也没有判断是否成功开辟空间
    strcpy(str,"hello");
    printf(str);
}

4.

//4. 提前释放了,并且没有把指针变量置空,str就成了野指针
int main()
{
    char* str = (char*)malloc(100);
    strcpy(str,"hello");
    free(str);//free后,str就成了野指针,需要置空(没有置空的话,str还是存的之前开辟空间的地址,只不过空间还给了操作系统),不然就会存在非法访问
    
    //非法访问
    if(str!=NULL)
    {
        strcpy(str,"world");
        printf(str);
    }
}

5. 柔性数组

也许你从来没有听说过**柔性数组(flexible array)**这个概念,但是它确实是存在的。

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

例如:

typedef struct st_type
{
 int i;
 int a[];//柔性数组成员(可以进行动态开辟的)
}type_a;

5.1 柔性数组的特点

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

注意:虽然 sizeof 返回的结果不会计算柔性数组,但是在计算大小时,柔性数组的对齐数要参与运算

image-20220305213555919

5.2柔性数组的使用

struct S
{
    int n;
    int arr[];//未知大小的数组 - 柔性数组,数组的大小可以动态开辟的
}
int main()
{
	//开辟的数组空间为5整形
    struct S* ps = (struct S*)malloc(sizeof(struct S)+5*sizeof(int));//空间连续,效率高(数组大小+你需要的空间大小)

    //继续追加20字节的空间
    struct S* ptr = realloc(ps,44);
    if(ptr!=NULL)
    {
        ps = ptr;
    }
    //开始使用
    //使用完后释放
    free(ps);
    ps = NULL;
}

5.3 与柔性数组进行对比

//还有一种就是先动态开辟结构体的空间 ,然后将里面的数组设置成指针int* arr,让它指向一块动态开辟的空间,后面释放就先释放arr,在释放结构体的,两块动态开辟的空间不连续(内存碎片增多)
struct S
{
    int n;
    int* arr;
}

struct S* ps = (struct S*)malloc(sizeof(S));
ps->arr = malloc(5*sizeof(int))  // arr指向一个空间,这个空间含有5个整形字节大小
    // ps与ps->arr都要进行free

5.4 柔性数组的好处

  1. 方便内存释放

    如果我们的代码是在一个给别人用的函数中,你在里面做了二次内存分配,并把整个结构体返回给用户。用户调用free可以释放结构体,但是用户并不知道这个结构体内的成员也需要free,所以你不能指望用户来发现这个事。所以,如果我们把结构体的内存以及其成员要的内存一次性分配好 了,并返回给用户一个结构体指针,用户做一次free就可以把所有的内存也给释放掉。

  2. 访问速度更快

    连续的内存有益于提高访问速度,也有益于减少内存碎片。(其实,我个人觉得也没多高了,反正你跑不了要用做偏移量的加法来寻址)

    关于柔性数组的介绍,推荐大家去看看这位大佬的博客:C语言结构体里的成员数组和指针,欢迎大家下来有问题可以私聊博主,大家一起学习进步。

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

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