目录
[2.1 malloc](#2.1 malloc)
[2.2 calloc](#2.2 calloc)
[4.3 对非动态内存就行free](#4.3 对非动态内存就行free)
[4.4 使⽤free释放⼀块动态开辟内存的⼀部分](#4.4 使⽤free释放⼀块动态开辟内存的⼀部分)
[4.5 对同⼀块动态内存多次释放](#4.5 对同⼀块动态内存多次释放)
[5 动态内存经典笔试题分析](#5 动态内存经典笔试题分析)
1.为什么会存在动态内存分配
向上面这种数据,只要我们创建好了,它们的大小就无法改变了
int main() {
int a = 10;
int arr[50];
return 0;
}
假如有一个班的数学成绩要存入int arr[50]数组中,我们难免会出现这种情况:
有班上有45个学生,而有的班上有55个学生,会发现数组有可能大了,这样浪费空间
有可能发现数组小了,这样成绩就没法全部存入。
这时c语言就引入了动态内存开辟,让程序员自己开辟,释放内存,这样就比较灵活了。
2.malloc和calloc
malloc和calloc都是定义在<stdlib.h>头文件中的
2.1 malloc
函数原型:void* malloc (size_t size);
-
输入你想要的空间大小(单位为字节) ,函数在堆内存中开辟一块连续的内存空间 ,并返回这块内存的起始地址(类型为void*)
-
使用malloc开辟的这块空间并不会初始化,里面内存为随机值
-
如果开辟失败(所开辟的空间过于巨大),则返回空指针(NULL),所以在使用之前,一定要判malloc的返回值
-
开辟的内存大小为0,这种行为是未定义的,取决于编译器,有的编译器可能脾气好给你随机返回一个指针,有的编译器可能就炸了
int main() {
int * p= (int *)malloc(sizeof(int)*5);
if (p == NULL) {
perror("malloc");
}
for (int i = 0; i < 5; i++) {
*(p + i) = i + 1;
}
for (int i = 0; i < 5; i++) {
printf("%d ", *(p+i));
}
free(p);
return 0;
}

2.2 calloc
函数原型:void* calloc (size_t num, size_t size);
-
calloc为num个大小为size的元素开辟一块空间,并且每个字节都初始化为0;
-
它与malloc的与别只在于calloc会为所开辟的空间进行初始化
int main() {
int* p = (int*)calloc(5,sizeof(int));
return 0;
}

如果想对目标空间快速的进行初始化的话可以使用calloc
3free和realloc
free和realloc都是定义在<stdlib.h>头文件中的
3.1free
当你使用完malloc,calloc开辟的内存后,总是要把内存还给操作系统的,因为可能人家也要用
这时c语言就为我们提供了free函数,它就是专门用来回收释放动态开辟的内存
函数原型:void free (void* ptr);
-
给用动态开辟内存的起始地址,函数free会回收掉这部分内存空间,不能给动态开辟内存地址以外的地址,这样释放的内存空间是错误的
-
指向的不是动态开辟的内存,其行为是未定义的
-
如果给了个空指针(NULL),free什么也不会做
int main() {
int* p = (int*)calloc(5,sizeof(int));
free(p);//回收动态开辟的内存
return 0;
}
3.2使用free的好习惯
当我们对free内存释放空间后,指向这块空间的指针就变成野指针看了
这里我们在c语言指针(1)野指针的形成和预防里讲过
所以当我们对空间free之后,要即使的对指向内存的指针进行NLLL的赋值
int main() {
int* p = (int*)calloc(5,sizeof(int));
free(p);//回收完之后,p指向的就是内存被释放的空间了
p = NULL;//给野指针进行赋值
return 0;
}
3.3realloc
realloc的出现可以让动态内存的管理更加灵活如果你发现你所开辟的动态内促大了或者小了,就可以用realloc函数来进行调整
函数原型:void* realloc (void* ptr, size_t size);
- 传入你想要调整的内存的指针ptr,size调整之后内存的大小
- 返回值为调整之后内存的起始地址
调小
把动态内存空间调整小的就不说了,无非就是将内存的部分使用权限回收
调大
但是动态内存空间跳大的情况就要好好说道说道了
情况一:
空间够开辟
当原先空间后面的空间 刚好可以满足你所需要的空间,那么它就会
- 直接在后面追加空间
- 原先数据不会变化
- 返回原先空间地址

情况二:
空间不够开辟

当原先空间后面的空间 不可以满足你所需要的空间,那么它就会
- 寻找一块新的空间进行开辟
- 拷贝原先空间里的数据到新空间
- 返回新空间的地址

需要注意的是
- 如果是情况二,原先的空间会被realloc函数自己释放(free)掉,不用我们手动处理
- realloc也会存在开辟空间失败的情况,所以直接不要拿原先的地址去接收开辟的地址,不然新的空间没有开辟成功,还会把原先的空间搞丢,最好判断地址一下是否为空(NULL)
- 如果你传了个空指针(NULL)让realloc帮你改变空间大小,那么它的作用等同于malloc,会直接帮你开辟一块新的空间,并返回地址
4.常见的动态内存的错误
4.1对空指针(NULL)进行解引用
int main() {
int* p = (int*)malloc(100000000000000); //开辟空间失败,返回空指针
/*if(p != NULL)*/ //使用前最好判断一下
*p = 20; //对NULL指针就行解引用,会出问题
return 0;
}
4.2对动态开辟的内存越界访问
int main() {
int* p = (int*)malloc(sizeof(int)*5);
if (p == NULL) {
perror("malloc");
}
for (int i = 0; i <= sizeof(int) * 5; i++) {
*(p + i) = i + 1; //当i=5时,对动态内存的越界访问
}
free(p);
return 0;
}
4.3 对非动态内存就行free
int main() {
int a = 10;
int* p = &a;
free(p); //程序会崩掉
return 0;
}
4.4 使⽤free释放⼀块动态开辟内存的⼀部分
int main() {
int* p = (int*)malloc(sizeof(int) * 5);
p++;//p不在指向起始地址
free(p); //程序会崩掉
return 0;
}
4.5 对同⼀块动态内存多次释放
int main() {
int* p = (int*)malloc(sizeof(int) * 5);
free(p);
free(p); //多次释放,程序会崩掉
return 0;
}
4.6动态开辟内存忘记释放(内存泄漏)
void test()
{
int* p = (int*)malloc(100);
if (NULL != p)
{
*p = 20; //当函数走完p会被销毁,想找到p空间也找不到了
}
}
int main()
{
test();
//导致了p空间无法被free造成的内存泄漏
while (1);
}
5 动态内存经典笔试题分析
为了更好的理解动态内存,我们来看几道经典的动态内存经典笔试题分析
题目一:
void GetMemory(char* p)
{
p = (char*)malloc(100); //确实有开辟空间,但是出去你就不知道它在哪里了
} //且出去后,使用权限就被回收了
void Test(void)
{
char* str = NULL;
GetMemory(str);
strcpy(str, "hello world"); //对指针进行操作出现问题
printf(str); //什么也不会打印
}
int main() {
Test();
return 0;
}
题目二:
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, int num) //传入str的地址
{
*p = (char*)malloc(num); //对str指向的地址进行空间开辟,没问题
}
void Test(void)
{
char* str = NULL;
GetMemory(&str, 100);
strcpy(str, "hello");
printf(str); //唯一的问题就是会造成内存泄露
/*free(str); //缺少的要进行的操作
str = NULL;*/
}
int main() {
Test();
return 0;
}
让GetMemory函数帮你做一件事,没有问题。只是在最后使用玩str指向的空间没有进行free
这样会造成内存的泄露。
所以我们要养成好习惯:
- 用完动态开辟的内存后为了防止内存泄漏:要及时的对内存进行释放(free)
- 进行内存的释放(free)后,为了防止指针变成野指针:要及时的对指针进行赋值空(NULL)
题目四:
void Test(void)
{
char* str = (char*)malloc(100);
strcpy(str, "hello");
free(str);//str不为空,且变成野指针
if (str != NULL)
{
strcpy(str, "world"); //非法访问
printf(str);
}
}
int main() {
Test();
return 0;
}