1.为什么要有动态内存分配
我们现如今已经掌握的内存开辟方式有
int main()
{int a = 0;int arr[30] = { 0 };return 0;
}
这两种方式,但是这种开辟空间的方式有两个特点:
1.空间开辟大小是固定的
2.数组在申明的时候,必须指定数组的长度,数组空间一旦确定了大小就不可更改了
但是对于空间的需求,不仅仅是上述的情况。有时候我们需要的空间大小在程序运行的时候才能知道,这样的话数组编译时开辟的空间可能就会不够用
于是C语言就引入了动态内存开辟,让我们自己可以申请和释放空间,提高了空间利用的灵活性
2.malloc和free
2.1malloc
C语言提供了一个动态内存开辟的函数
头文件是stdble
void* malloc(size_t size);
-malloc函数向内存申请一块连续可用的空间,并返回指向这块空间的指针。
-如果开辟成功,则返回一个指向开辟好的内存的指针
-如果开辟失败,则返回一个空指针(NULL)
-返回值的类型是void*,所以malloc函数并不知道开辟空间的类型,需要我们根据需要进行转换
-如果参数size为0,malloc的行为是标准是未定义的,取决于编译器
#include<stdlib.h>
int main()
{int * p = (int *)malloc(10 * sizeof(int));int i = 0;for (i = 0; i < 10; i++){*(p + i) = i;printf("%d ", *(p + i));}if (p == NULL){perror("malloc");return 1;}return 0;
}
上述是关于用malloc创建10个整型空间
malloc申请的空间与数组空间的区别:
1.动态内存的大小是可以调整的
2.存放的位置不一样
2.2free
C语言提供了另外一个函数free,专门是用来做动态内存的释放和回收的
头文件:stdlib.h
函数原型:
void free (void* ptr);
代码应用实例:
free(p);//p变成了野指针//p指向的空间不属于当前程序//但是还是可以找到原来的空间(空有地址无法使用)p = NULL;//为了更彻底,把p这个野指针用空指针拴住
free函数用来释放动态开辟的内存
如果参数ptr指向的空间不是动态开辟的,那么free函数的行为是未定义的
例子:
int main()
{int arr[20] = { 0 };free(arr);return 0;
}
-如果参数ptr是空指针,那么函数什么都不做
例子:
malloc和free一般都是一块使用
3.calloc和realloc
3.1calloc
C语言提供了一个函数叫calloc,calloc函数也用来动态内存分配
头文件:stdlib.h
函数模型:
void* calloc (size_t num, size_t size);
函数的功能是为num大小为size的元素开辟一块空间,并且把空间中每个字节初始化为0
与函数malloc的区别只在于calloc会返回地址之前把申请的空间的每个字节初始化为0
calloc函数的应用例子:
#include<stdlib.h>
int main()
{int* p = (int*)calloc(10, sizeof(int));int i = 0;for (i = 0; i < 10; i++){printf("%d ", p[i]);}if (p == NULL){return 1;}return 0;
}
运行结果为
2.2realloc
realloc函数使得动态内存管理更加灵活
有时候我们会发现申请的内存空间太小了,有时候又会觉得太大了,这时候我们就需要realloc函数对内存空间进行灵活的调整
头文件:stdlib.h
函数模型:
void* realloc (void* ptr, size_t size);
-ptr是要调整的内存
-size是调整之后的新大小
-返回值为调整之后的内存起始位置
-这个函数调整原内存空间大小的基础上,还会将原来内存中的数据移动到新的空间
-realloc在调整内存空间的是存在两种情况:
1.原有空间之后有足够大的空间:
直接在旧空间之后开辟空间,返回原来的空间地址
2.原有空间之后没有足够大的空间:
realloc函数直接在内存的堆区找一块新的满足大小的空间,并且将旧的数据拷贝到新的空间,然后释放旧的空间,返回新的地址
realloc函数使用的例子
#include<stdlib.h>
int main()
{int* p = (int*)calloc(10, sizeof(int));int i = 0;for (i = 0; i < 10; i++){printf("%d ", p[i]);}if (p == NULL){perror("calloc");return 1;}//如果空间不够int* ptr = (int*)realloc(p, 20 * sizeof(int));if (ptr == NULL){perror("realloc");return 1;}else{p = ptr;}free(p);p = NULL;return 0;
}
tips:
realloc不仅可以调整内存空间,还可以想malloc和calloc一样创建动态内存空间
int main()
{int* p = (int*)realloc(NULL, 40);//==malloc(40);return 0;
}
4.常见的动态内存的错误
4.1对NULL指针的解引用操作
不注意p是否为空指针的话就会出现上述代码中的错误
加上判断的话编译器就不会报错了
4.2对动态开辟空间的越界访问
4.3对非动态开辟内存使用free释放
//对非动态空间进行释放
int main()
{int arr[3] = { 0 };int* p = arr;free(p);p = NULL;return 0;
}
运行结果:
4.4使用free释放一部分动态内存空间
int main()
{int* p = (int*)malloc(10*sizeof(int));if (p == NULL){perror("malloc");return 1;}int i = 0;for (i = 0; i < 5; i++){*p = i;p++;//这里p的地址移动了,不再是原来的p}free(p);p = NULL;return 0;
}
运行结果:
4.5对同一块动态内存多次释放
int main()
{int* p = (int*)malloc(10*sizeof(int));if (p == NULL){perror("malloc");return 1;}int i = 0;for (i = 0; i < 40/*这里越界了*/; i++){p[i] = i;}free(p);//......free(p);//多次释放并且第一次没有将p赋为NULLp = NULL;return 0;
}
运行结果:
但如果第一次内存释放已经将p变成空指针NULL,那么多次释放也没事
因为p是空指针,所以函数free什么也不做
4.6动态开辟内存忘记释放(内存泄漏)
void test()
{int flag = 1;int* p = (int*)malloc(10 * sizeof(int));if (p == NULL){perror("malloc");return;}//使用......if (flag == 0)return;free(p);//看似进行了释放//但是前面的条件有一个符合就会提前结束函数,便会漏掉释放内存p = NULL;
}int main()
{test();//如果提前释放,那么就会发生内存泄漏//malloc创建的动态内存空间无法释放,也无法被找到//无法被找到是因为p只在test函数中使用,test函数也没有返回地址return 0;
}
总的来说,动态内存管理是一把双刃剑,既能提供灵活的内存管理方式,又会具有一定的风险
5.动态内存题目练习
5.1
char *GetMemory(void)
{char p[] = "hello world";return p;
}
void Test(void)
{char *str = NULL;str = GetMemory();printf(str);
}
int main()
{Test();return 0;
}
运行结果:程序会崩溃
解析:(注释)
void GetMemory(char* p)
{p = (char*)malloc(100);
}
void Test(void)
{char* str = NULL;GetMemory(str);//无返回值,创建malloc动态内存后也没有释放,也没有地址//造成内存泄露strcpy(str, "hello world");//这里对str这个NULL空指针进行了解引用//所以程序会崩溃printf(str);
}
int main()
{Test();return 0;
}
正确代码:
或者不用二级指针,只是将函数GetMemory加个返回值,返回malloc动态空间的地址
5.2
char* GetMemory(void)
{char p[] = "hello world";return p;
}
void Test(void)
{char* str = NULL;str = GetMemory();printf(str);
}
int main()
{Test();return 0;
}
运行结果:
解析:(注释)
char* GetMemory(void)
{char p[] = "hello world";return p;
}
void Test(void)
{char* str = NULL;str = GetMemory();//str的值变成了GM函数中p的地址printf(str);//printf可以通过str找到原来char p[] 的空间地址//但是没有访问权限,char p []只在GM函数中有效//所以str相当于野指针,无法访问,所以打印出来了这些
}
int main()
{Test();return 0;
}
str的使用是个返回栈空间的问题
5.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;
}
运行结果:
看似没有问题,hello也可以打印出来,但是遗漏了一个重要的问题
malloc这个动态内存没有进行释放,使得内存泄漏
改正:
void GetMemory(char** p, int num)
{*p = (char*)malloc(num);
}
void Test(void)
{char* str = NULL;GetMemory(&str, 100);strcpy(str, "hello");printf(str);free(str);str = NULL;
}int main()
{Test();return 0;
}
5.4
void Test(void){char *str = (char *) malloc(100);strcpy(str, "hello");free(str);if(str != NULL){strcpy(str, "world");printf(str);}}
int main()
{Test();return 0;
}
运行结果:
看似没问题,但是str使用了未初始化的内存,意思是str是个野指针,这里是野指针的非法访问,代码是错误的
改正:
void Test(void)
{char* str = (char*)malloc(100);strcpy(str, "hello");free(str);str = NULL;if (str != NULL)//本意应该是为检测str是否转化为NULL{strcpy(str, "world");printf(str);}
}
int main()
{Test();
6.柔性数组
C99中,结构体中的最后一个元素允许是未知大小的数组
两种写法:(有些编译器只能由其中一个写法)
struct S
{int a;float b;char c;int arr[];//未知数组
};struct S2
{int a;float b;int arr[0];
};
6.1柔性数组的特点
1.-结构中的柔性数组成员前面必须至少一个其他成员
2.-sizeof返回的这种结构大小不包括柔性数组的内存
3.-包括柔性数组成员的结构用malloc()函数进行内存分配,并且分配的内存应该大于结构的大小,以适应柔性数组的预期大小
struct S
{int n;int arr[];
};
int main()
{struct S s;//这个只创建了有int的结构体,不包含柔性数组的内存空间struct S* pf = (struct S*)malloc(sizeof(struct S) + 20 * sizeof(int));//这才是创建有柔性数组的结构体的代码return 0;
}
应用:
struct S
{int n;int arr[];
};
int main()
{struct S* pf = (struct S*)malloc(sizeof(struct S) + 20 * sizeof(int));if (pf == NULL){perror("malloc");return 1;}pf->n = 10;int i = 0;for (i = 0; i < 10; i++){pf->arr[i] = i;printf("%d\n", pf->arr[i]);}return 0;
}
运行结果:
由于是malloc创建的动态内存空间,所以可以用realloc来对内存进行调整
struct S
{int n;int arr[];
};
int main()
{struct S* pf = (struct S*)malloc(sizeof(struct S) + 20 * sizeof(int));if (pf == NULL){perror("malloc");return 1;}pf->n = 10;int i = 0;for (i = 0; i < 10; i++){pf->arr[i] = i;printf("%d\n", pf->arr[i]);}//调整空间struct S* ptr = (struct S*)realloc(pf, (sizeof(struct S) + 40 * sizeof(int)));if (ptr != NULL){pf = ptr;ptr = NULL;}else{return 1;}//使用//释放空间free(pf);pf = NULL;return 0;
}
6.2柔性数组的优势
还有一种类型也可以有柔性数组的作用
就是将柔性数组变成指向malloc创建动态内存的指针
struct S
{int n;int* arr;
};
int main()
{struct S* s = (struct S*)malloc(sizeof(struct S));int* pl = (int*)malloc(20 * sizeof(int));if (pl == NULL){perror("malloc");return 1;}else{s->arr = pl;}//use//......//调整内存int* ptr = (int*)realloc(pl, 40 * sizeof(int));if (ptr != NULL){pl = ptr;}else{perror("realloc");return 1;}//use......//endingfree(s->arr);free(pl);pl = NULL;return 0;
}
两者对比我们可以发现
柔性数组既方便内存的释放(释放一次即可),并且有利于提高访问速度