1.为什么存在动态内存分配
int i=0;
int arr[20]={0};
- 上述代码开辟空间大小是固定的
- 有时候我们要再程序运行的时候才能知道具体需要多少空间
2.动态内存函数介绍
1.malloc
void* malloc (size_t size);
这个函数向内存申请一块连续的空间,并返回这块地址的指针
- 申请成功,返回一个开辟好空间的指针
- 申请失败,返回NULL
- sizeof(0):未定义行为,具体由编译器决定
- 返回值由void *接收,具体使用交给操作者
2.free
void free (void* ptr);
作用是释放由malloc、calloc、realloc开辟的内存
- 如果指针ptr指向的内存不是有动态内存函数开辟的,那么free(ptr)是未定义行为,取决于编译器
- 如果是free(NULL),则什么也不做
- 该函数没有返回值
3.calloc
void* calloc (size_t num, size_t size);
函数的功能是Allocate and zero-initialize array,也就是分配和零初始化数组
- 它会开辟一个num*size的空间,并且把每个元素的值都初始化成0
- size如果是0,那么返回值是未定义的,可能是NULL,也可能不是,但是都不能被解引用
- 它和malloc的区别就是将内存空间的每个字节都初始化成了0
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdlib.h>
int main()
{
int* ptr = (int*)malloc(sizeof(int) * 10);
int* p = (int*)calloc(10, sizeof(int));
int i = 0;
for (i = 0; i < 5; i++)
{
ptr[i] = i + 1;
p[i] = i + 1;
}
free(ptr);
ptr = NULL;
free(p);
p = NULL;
return 0;
}
我们可以看到malloc没有初始化,calloc初始化了
4.realloc
void* realloc (void* ptr, size_t size);
该函数的功能是Reallocate memory block ,也就是重新分配内存块
- 只能重新分配malloc和calloc开辟的空间
- size为0属于未定义行为
- ptr是需要调整的内存地址,size是需要开辟的字节大小
- void*用来接收重新分配内存块的地址
- 如果ptr==NULL,就实现了malloc的功能
分两种情况
第一种情况,指针在后面重新找一块足够大的地址,将原来的数据拷贝过去
第二种情况,后面的空间足够,返回原来的指针
- 开辟失败则返回NULL
所以我们我们一般这样操作
int main()
{
int* ptr = calloc(10, sizeof(int));
int *p=realloc(ptr, sizeof(int) * 15);
//避免后面返回空指针然后解引用这种错误
if (p != NULL)
{
ptr = p;
}
free(ptr);
ptr = NULL;
return 0;
}
3. 常见的动态内存错误
3.1 对NULL指针的解引用操作
void test1() { int* ptr = malloc(INT_MAX); *ptr = 1; }
3.2 对动态开辟空间的越界访问
void test2() { int* ptr = calloc(5, sizeof(int)); int i = 0; for (i = 0; i < 10; i++) { printf("%d ", ptr[i]); } }
3.3 对非动态开辟内存使用free释放
void test3() { int i = 0; int* ptr = &i; free(ptr); ptr = NULL; }
3.4 使用free释放一块动态开辟内存的一部分
void test4() { int* ptr = malloc(sizeof(int)); ptr++; free(ptr); ptr = NULL; }
3.5 对同一块动态内存多次释放
void test5() { int* ptr = calloc(10, sizeof(char)); free(ptr); free(ptr); }
3.6 动态开辟内存忘记释放(内存泄漏)
void test6() { int* ptr = (int *)calloc(10, sizeof(int)); if (NULL != ptr) { *ptr = 10; } }
系统不会报错,但是会导致系统内存越来越小,就是我们所说的内存泄漏
4.柔性数组
4.1什么是柔性数组?
struct Node
{
int i;
char str[0];
};
或者这个
struct Node
{
int i;
char str[];
};
特点:
- 柔性数组前面必须有一个成员变量
- sizeof(结构体类型)不包括柔性数组的大小
- 用动态函数来维护或者说开辟柔性数组的空间
4.2柔性数组的使用
//代码1
typedef struct Node
{
int i;
char str[];
}Node;
int main()
{
Node *n= (Node*)malloc(sizeof(Node)+20);
n->i = 100;
strcpy(n->str, "hello world");
printf("%d %s", n->i, n->str);
return 0;
}
4.3柔性数组的优势
上述代码我们其实可以自己写
typedef struct Node
{
int i;
int *str;
}Node;
int main()
{
Node* n = (Node*)malloc(sizeof(Node));
if (n == NULL)
{
perror("malloc");
return 1;
}
n->i = 100;
int* p = (int*)malloc(sizeof(int) * 20);
if (p == NULL)
{
free(n);
n = NULL;
perror("malloc");
return 1;
}
n->str = p;
int i = 0;
for (i = 0; i < 20; i++)
{
(n->str)[i] = i + 1;
}
for (i = 0; i < 20; i++)
{
printf("%d ", (n->str)[i]);
}
free(n->str);
n->str = NULL;
free(n);
n = NULL;
return 0;
}
1.方便内存释放
我们的代码做了二次内存分配,当需要free这段结构体空间的时候,别人并不知道结构体内容也需要释放,而且这里必须先释放结构体变量的内存分配,再释放整个结构体
2.有利于访问速度
连续的空间有利于访问速度,而且柔性数组使用了一整块内存空间,我们的代码则使用了两块不连续空间,就有可能产生内存碎片,使我们无法有效利用内存空间了