欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 文旅 > 手游 > C语言-动态内存管理

C语言-动态内存管理

2025/3/20 23:16:08 来源:https://blog.csdn.net/life_time_/article/details/146346162  浏览:    关键词:C语言-动态内存管理

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;
}

两者对比我们可以发现

柔性数组既方便内存的释放(释放一次即可),并且有利于提高访问速度

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

热搜词