文章目录
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。