C语言笔记15:动态内存管理

文章目录

C语言笔记15:动态内存管理

内存区域划分

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

动态内存分配,就是程序运行时才知道要分配多少内存。

C99标准之后支持声明数组的时候给数组大小一个变量,但是不能初始化这个数组。虽然说这个特性一定程度上支持了运行时分配内存,但是缺点在于,栈空间大小是一次性开辟好的,如果这个变量传入太大的数字,就会导致栈溢出。而栈空间大小一般都不大。

动态内存分配函数介绍

malloc

c 复制代码
void* malloc(size_t size);
  • 开辟失败返回NULL
  • 传入0标准未定义

free

c 复制代码
void free(void* ptr);
  • ptr指向空间不是动态开辟的是标准未定义的
  • ptr不是开辟空间的起始位置会出错
  • ptr为NULL什么都不做
  • ptr指向一块已经释放过的内存会出错

calloc

c 复制代码
void* calloc(size_t num,size_t size);
  • 将num个大小为size的空间初始化为0
  • 失败返回NULL

realloc

c 复制代码
void* realloc(void* ptr,size_t size);
  • 如果ptr为NULL,那就是malloc,如果ptr为其他非NULL但又不是动态开辟的空间,行为未定义
  • 返回新开辟的空间起始位置
  • 失败也返回NULL

使用:

c 复制代码
#include <stdlib.h>
#include <stdio.h>

int main()
{
	int* ptr = (int*)malloc(100);
	if(ptr != NULL)
	{
		//...
	}
	else
	{
		return 1;
	}
	
	//扩容1
	ptr = realloc(ptr,200);
	
	//扩容2
	int* p = realloc(ptr,200);
	if(p != NULL)
	{
		ptr = p;
        p = NULL;
	}
	else
	{
		return 2;
	}
    
	free(ptr);

	return 0;
}

扩容2的做法要比扩容1好,如果扩容失败,至少原数据没有丢失,而扩容1的做法如果扩容失败了,连原来的数据也丢失了。

常见动态内存错误

不判断返回值,对NULL解引用

c 复制代码
void test()
{
	int *ptr = (int*)malloc(sizeof(int));
	*ptr = 20;
}

越界访问

c 复制代码
void test()
{
	int* ptr = (int*)malloc(sizeof(int)*10);
	if(ptr == NULL)
		return;
	for(int i = 0;i <= 10;i++)
	{
		*(ptr + i) = i;
	}
	free(ptr);
}

除了上面两个,还有free的错误:

  • 对一块空间释放两次
  • 释放空间不是申请空间的起始位置
  • 对非动态开辟的空间释放
  • 开辟的空间忘记释放导致内存泄漏

练习

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

str还是NULL,对NULL进行strcpy会直接引发异常中断。

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

对一个释放的栈空间访问,可以访问,返回未知值,烫烫烫...xx。

c 复制代码
void GetMemory(char **p, int num)
{
	*p = (char *)malloc(num);
}
void Test(void)
{
    char *str = NULL;
    GetMemory(&str, 100);
    strcpy(str, "hello");
    printf(str);
}

正确

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

在这个程序可能不会出错,但是对一个free释放的空间访问,是极其危险的一件事情。这就是所谓的悬挂指针。一个空间free掉之后没有对这个指针置空,此时这个指针指向的空间已经不属于程序了,但是值还保留着。

就像是去宾馆住了一晚上,退房后却没还房卡,酒店已经清理了房间,下次有人入住的时候你要是拿着房卡串门就尴尬了。

柔性数组

最后一个成员是大小未知的数组,就是柔性数组

c 复制代码
struct soft
{
	int data;
	int arr[0];
};

//或者
struct soft
{
	int data;
	int arr[];
};
  • 柔性数组不参与大小计算,所以柔性数组前面必须有一个成员变量
  • 有柔性数组成员的结构应该进行动态内存分配

为什么有柔性数组

对比两种方案

复制代码
typedef struct A
{
	int num;
	int* ptr;
}A;

int main()
{
	A* pa = (A*)malloc(sizeof(A));
	pa->num = 10;
	pa->ptr = (int*)malloc(sizeof(int) * pa->num);
	
	free(pa->ptr);
	free(pa);
	
	return 0;
}
c 复制代码
typedef struct A
{
	int num;
	int arr[];
}A;

int main()
{
	A* pa = (A*)malloc(sizeof(A) + sizeof(int) * 10);
	pa->num = 10;
	
	free(pa);
	
	return 0;
}

如果A*是函数的返回值呢?struct A定义在了其他头文件中?
这就很容易出错,调用函数的人不知道struct A是一个二次内存分配的结构体,他们可能进行一次free。

相关推荐
一只鹿鹿鹿21 分钟前
智算算力、AI-芯片与产品解析(PPT文件)
大数据·运维·网络·人工智能·物联网·1024程序员节
开源必胜25 分钟前
Socket、异步非阻塞 I/O 与 NGINX、Netty 模型
网络·tcp/ip·nginx
沫璃染墨37 分钟前
从零入门计算机网络系列(十一):HTTP协议进阶——从重定向原理到GET/POST与Fiddler抓包
linux·服务器·开发语言·网络·c++·tcp/ip·html
蓝祁子yoky1 小时前
Qt学习日记 Day13:网络编程(一)-TCP基础与Socket实战
网络·qt·学习
箓维2 小时前
TCP Socket的进阶实现
网络·笔记·tcp/ip
二哈赛车手2 小时前
新人笔记---hermes定时功能设计,具体讲解以及可优化点
java·人工智能·笔记·spring·ai·typescript
揽星河..2 小时前
【C语言】分支和循环
c语言·c++·算法
小羊没烦恼!2 小时前
关于分布式锁原理的一些学习与思考-redis分布式锁,zookeeper分布式锁
java·服务器·网络·学习·负载均衡
小Ti客栈3 小时前
Netty 零拷贝原理与实战
运维·服务器·网络
国科安芯3 小时前
商业光学遥感载荷焦平面供电链的低噪声稳压器研究综述——以国科安芯ASP7A84AS型3A低压差线性稳压器为器件级参照
大数据·网络·嵌入式硬件·平面·卫星·商业航天·抗辐射