飞道的博客

动态内存分配/管理

270人阅读  评论(0)

目录

1、为什么要有动态内存分配

2、动态内存函数介绍

1、malloc

2、free

3、calloc

​编辑

4、realloc

 3、动态内存常见的错误

4、动态内存开辟相关好题

5、c/c++程序内存开辟示意图


int a,  int arr[10]  是固定地向内存申请连续的一块空间,但不能变长或变短随时调整。

在我们之前写的静态版通讯录中,我们创建了一个peopleinfo类型的数组date[100]用来存放100个人的个人信息,但是当我们的人员信息较小时,100个结构体显得有些浪费,而当我们所需存放的信息超过100时又不够用,随之我们就得修改这个数字,但是学了动态内存管理之后,我们可以动态地分配内存空间变大或变小,从而有效利用空间。

1、为什么要有动态内存分配

int val = 20;//在栈空间上开辟四个字节
char arr[10] = {0};//在栈空间上开辟10个字节的连续空间
但是上述的开辟空间的方式有两个特点:
1. 空间开辟大小是固定的。
2. 数组在申明的时候,必须指定数组的长度,它所需要的内存在编译时分配。
但是对于空间的需求,不仅仅是上述的情况。有时候我们需要的空间大小在程序运行的时候才能知道,
那数组的编译时开辟空间的方式就不能满足了。
这时候就只能使用动态内存开辟了。

2、动态内存函数介绍

1、malloc

#include<stdlib.h>

这个函数向内存申请一块连续可用的空间,并返回指向这块空间的指针。
如果开辟成功,则返回一个指向开辟好空间的指针。
如果开辟失败,则返回一个NULL指针,因此malloc的返回值一定要做检查。
返回值的类型是 void* ,所以malloc函数并不知道开辟空间的类型,具体在使用的时候使用者自己
来决定。
如果参数 size 为0,malloc的行为是标准是未定义的,取决于编译器。
 

先在堆区上动态申请一定空间,使用后应该还给操作系统,如果不主动还,程序结束后会自动还,但是如果程序一直不结束,就一直“占着不用”,就会造成空间的浪费。

2、free

用来释放、归还申请的内存


  
  1. int main()
  2. {
  3. //申请40个字节,用来存放10个整型
  4. int* p = ( int*) malloc( 40);
  5. if (p == NULL)
  6. {
  7. printf( "%s\n", strerror(errno));
  8. return 1;
  9. }
  10. //存放 1--10
  11. int i = 0;
  12. for (i = 0; i < 10; i++)
  13. {
  14. *(p + i) = i + 1;
  15. }
  16. for (i = 0; i < 10; i++)
  17. {
  18. printf( "%d ", *(p+i));
  19. }
  20. //free释放申请的内存
  21. free(p);
  22. return 0;
  23. }

 

 用malloc申请后,内存中为随机值,使用时可给它们赋值,当free后,又变为随机值。

 仔细观察,我们可以发现,虽然这块空间的值发生了变化,但是指针p指向的地址free前后没有变化。因此,当我们free还给操作系统后,p仍指向这块空间。此时*p就会导致非法访问,因此我们需要将p制为NULL,避免非法访问。


  
  1. free(p);
  2. p = NULL;

总结:malloc申请空间后不会初始化,使用前要判断是否成功申请(是否返回NULL),使用后要free还给操作系统,然后将用于接收这块内存空间的指针p置为NULL。

 申请失败时返回NULL,并打印错误原因(没有足够空间)。

void  free(void * ptr)

free函数用来释放动态开辟的内存。
如果参数 ptr 指向的空间不是动态开辟的,那free函数的行为是未定义的。
如果参数 ptr 是NULL指针,则函数什么事都不做。

3、calloc

 

由上图我们可以知道,malloc不会初始化,calloc会将每个元素先初始化为0,可以按需用。由于calloc需要初始化,效率比malloc稍低,此外没有其它区别,都需要进行相关步骤。

4、realloc


  
  1. int main()
  2. {
  3. int* p = ( int*) malloc( 5 * sizeof( int));
  4. if ( NULL == p)
  5. {
  6. perror( "malloc");
  7. return 1;
  8. } //使用
  9. int i = 0;
  10. for (i = 0; i < 5; i++)
  11. {
  12. p[i] = 1;
  13. } //空间不够,增加5个整型的空间
  14. //此时不能用p接收
  15. int* ptr = ( int*) realloc(p, 10 * sizeof( int));
  16. //先用ptr接收,再赋给p,防止返回NULL,p找不到原来的数据
  17. if (ptr != NULL)
  18. {
  19. p = ptr;
  20. ptr = NULL; //释放但不置空,需手动置空
  21. }
  22. //继续使用
  23. for (i = 0; i < 10; i++)
  24. {
  25. printf( "%d ", p[i]);
  26. } //realloc会把旧的空间释放,不用自己释放
  27. free(p);
  28. p = NULL;
  29. }

 3、动态内存常见的错误

1、对NULL解引用(未判断是否为空)

2、非法访问内存,越界访问(解引用野指针)

3、对非动态开辟内存的空间用free释放


  
  1. void test()
  2. {
  3. int a = 10;
  4. int *p = &a;
  5. free(p); //ok?
  6. }

对栈区的空间进行释放(x)

4、使用free释放一块动态开辟内存的一部分


  
  1. void test()
  2. {
  3. int *p = ( int *) malloc( 100);
  4. p++;
  5. free(p); //p不再指向动态内存的起始位置
  6. p= NULL
  7. }

5、对同一块内存多次释放


  
  1. void test()
  2. {
  3. int *p = ( int *) malloc( 100);
  4. free(p);
  5. free(p); //重复释放
  6. }

free一个NULL空指针时,什么事都不会发生。

malloc等函数是申请一块空间,获得使用它的权限,free释放后,将权限还给操作系统,如果再访问或是释放,就是非法访问。
6、动态开辟的内存忘记释放(内存泄漏)

malloc和free要成对出现,防止出现内存泄漏


  
  1. int* test()
  2. //函数内部进行了malloc操作,返回了malloc开辟的空间的起始地址
  3. //谁接收了 要记得释放和置空
  4. {
  5. int* p = ( int*) malloc( 100);
  6. if ( NULL == p)
  7. {
  8. return 1;
  9. }
  10. return p;
  11. }
  12. int main()
  13. {
  14. int* ptr = test();
  15. free(ptr);
  16. ptr = NULL;
  17. }

4、动态内存开辟相关好题


  
  1. void GetMemory(char *p)
  2. {
  3. p = ( char *) malloc( 100);
  4. }
  5. void Test(void)
  6. {
  7. char *str = NULL;
  8. GetMemory(str);
  9. strcpy(str, "hello world");
  10. printf(str);
  11. }
  12. int main()
  13. {
  14. Test();
  15. }

这里GetMemory函数是值传递,且没有返回值,对str无影响。因此str还是指向NULL,即0地址处,然后调用strcpy需访问0地址处内容,导致非法访问。

同时,上面用malloc申请了一块空间,但是函数内没有释放,函数销毁后,p也销毁,这块空间就会内存泄漏。记得malloc要与free搭配使用。

修改后,可传入&str,用char**p接收,*P=(char*)malloc(100),使p指向开辟的100byte,进而存放拷贝的内容,打印后free(str) str=NULL

或者将p(char*)直接返回,用str接收,因为没有free,所以malloc(100)仍然存在,进而可以strcpy,如果此时不是malloc申请,而是利用数组,函数销毁后,数组的空间也会释放,如果再进行打印就不行了。(如下图)这类问题简称为 返回栈空间地址的问题

可以在p前面加上static使其变为静态区变量,函数销毁后它不会销毁,或者去掉[],p变为char*类型,即从数组变为常量字符串,常量字符串也是在静态区,也就是把在栈区存储的数据放入静态区中存储,从而避免了返回栈空间地址的问题。

 printf(str)括号内直接加str是可以的,str是一个地址,例如printf(“hehe”),括号内有引号+字符串,也就是首字符的地址,相当于括号内直接加一个地址,最终结果都是打印字符串。


  
  1. void GetMemory(char **p, int num)
  2. {
  3. *p = ( char *) malloc(num);
  4. }
  5. void Test(void)
  6. {
  7. char *str = NULL;
  8. GetMemory(&str, 100);
  9. strcpy(str, "hello");
  10. printf(str);
  11. }
  12. int main()
  13. {
  14. Test();
  15. }

这段代码的整体思路没有问题,但是会导致内存泄漏,  malloc等函数需要与free共同使用


  
  1. void Test(void)
  2. {
  3. char *str = ( char *) malloc( 100);
  4. strcpy(str, "hello");
  5. free(str);
  6. }
  7. if(str != NULL)
  8. {
  9. strcpy(str, "world");
  10. printf(str);
  11. }

free(str)后已经没有权限访问了,但后面又调用strcpy访问空间,导致非法访问

开辟--释放--置空

习题来自《高质量C/C++编程》

5、c/c++程序内存开辟示意图

 1. 栈区(stack):在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结
束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是
分配的内存容量有限。 栈区主要存放运行函数而分配的局部变量、函数参数、返回数据、返
回地址等。
2. 堆区(heap):一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收 。分
配方式类似于链表。
3. 数据段(静态区)(static)存放全局变量、静态数据。程序结束后由系统释放。
4. 代码段:存放函数体(类成员函数和全局函数)的二进制代码,只读常量(不能被修改),字符串常量。


转载:https://blog.csdn.net/lzfnb666/article/details/128700281
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场