• 【C语言进阶】动态内存管理常见错误


    📚作者简介:爱编程的小马,正在学习C/C++,Linux及MySQL..

    📚以后会将数据结构收录为一个系列,敬请期待

    ● 本期内容会给大家讲解动态内存空间开辟的常见错误以及几个经典的面试题

    目录。


    1. 常见错误

     1.1 对NULL指针的解引用

    1. #include
    2. #include
    3. int main()
    4. {
    5. int* p = (int*)malloc(INT_MAX);
    6. *p = 20;
    7. int i = 0;
    8. for (i = 0; i < 10; i++)
    9. {
    10. printf("%d ", p[i]);
    11. }
    12. free(p);
    13. p=NULL;
    14. return 0;
    15. }

    输出结果:

    解析:为什么程序输出挂掉了,由于malloc开辟空间过大,导致无法成功开辟而返回空指针,如果没有对p进行判断就对指针解引用是很危险的,一定要在使用之前判断是否为空指针

    1.2 对动态开辟空间的越界访问

    1. #include
    2. #include
    3. int main()
    4. {
    5. int* p = (int*)malloc(40); //正常开辟了40个字节的空间
    6. if (p == NULL)
    7. {
    8. perror("malloc");
    9. return 1;
    10. }
    11. int i = 0;
    12. for (i = 0; i <= 10; i++)
    13. {
    14. *(p + i) = i;
    15. }
    16. free(p);
    17. p == NULL;
    18. return 0;
    19. }

    解析:本身开辟了40个字节的空间,但是我是用了44个字节,这就是越界访问。越界访问在程序运行中不会报错,但是要防止这种情况的发生。

    1.3 对非动态开辟的内存进行释放

    1. #include
    2. #include
    3. int main()
    4. {
    5. int a = 0;
    6. int* pa = &a;
    7. free(pa);
    8. pa = NULL;
    9. }

    运行结果: 

    解析:在动态内存管理一文讲过,free对于不是动态内存开辟的空间是未定义的,是不允许的

    1.4 使用free释放动态内存空间开辟的一部分

    1. #include
    2. #include
    3. int main()
    4. {
    5. int* p = (int*)malloc(40);
    6. if (p == NULL)
    7. {
    8. perror("malloc");
    9. return 1;
    10. }
    11. int i = 0;
    12. for (i = 0; i < 5; i++)
    13. {
    14. *p = 5;
    15. p++;
    16. }
    17. free(p);
    18. p = NULL;
    19. return 0;
    20. }

    输出结果:

    解析:p不指向起始地址了,所以释放的时候,p之前的空间就找不到了,无法释放。

    1.5 对同一块内存多次释放

    1. #include
    2. #include
    3. int main()
    4. {
    5. int* p = (int*)malloc(40);
    6. if (p == NULL)
    7. {
    8. perror("malloc");
    9. return 1;
    10. }
    11. int i = 0;
    12. for (i = 0; i < 5; i++)
    13. {
    14. *(p+i) = 5;
    15. }
    16. free(p);
    17. free(p);
    18. p = NULL;
    19. }

    输出结果:

    解析:对同一块内存多次释放,其实最简单的避免的方法就是置为空指针。

    1.6 动态开辟内存忘记释放(内存泄漏)

    1. #include
    2. #include
    3. void test()
    4. {
    5. int* p = (int*)malloc(40);
    6. if (p == NULL)
    7. {
    8. perror("test");
    9. return;
    10. }
    11. *p = 20;
    12. }
    13. int main()
    14. {
    15. test();
    16. while (1);
    17. return 0;
    18. }

    输出结果:

    解析:可以看到,输出结果是一个死循环。但是我们进函数内部看下,首先malloc先申请了40个字节的空间,进来后指针变量p去维护,但是出了函数作用域,我忘记释放这块内存了,导致这块内存想释放释放不了,想找也找不到,程序也无法结束,这就是内存泄露。所以一定要记住,使用完内存后及时释放内存。

    2. 四道经典的笔试题

    2.1 第一题

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

    解析:首先进入Test函数,str指向一块存放NULL的空间,调GetMemory函数,p也指向这块NULL空间,因为形参是实参的临时拷贝,p指向malloc开辟的100个字节的空间。但str指向的空间变了吗?没有,所以实际上是strcpy(NULL,"hello world"),这个是错的,所以程序挂掉了,一起来看下运行结果:

    如何改造一下呢?使它变成一个正确的代码。我们不就是想利用GetMemory函数创建100个字节的空间,用str去维护,那我就可以传str的地址过去是不是就可以了。

    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. if (str == NULL)
    10. {
    11. perror("GetMemory:");
    12. return;
    13. }
    14. strcpy(str, "hello world");
    15. printf(str);
    16. free(str);
    17. str = NULL;
    18. }
    19. int main()
    20. {
    21. Test();
    22. return 0;
    23. }

    运行结果: 

    2.2 第二题

    1. #include
    2. #include
    3. #include
    4. char* GetMemory(void)
    5. {
    6. char p[] = "hello world";
    7. return p;
    8. }
    9. void Test(void)
    10. {
    11. char* str = NULL;
    12. str = GetMemory();
    13. printf(str);
    14. }
    15. int main()
    16. {
    17. Test();
    18. return 0;
    19. }

    解析:首先进入test函数,先创建一个指针变量指向一块空间,里面存放空指针。进入GetMemory函数,首先用一个字符数组接收常量字符串,return p 实际上就是返回了这个常量字符串的首元素地址,变量出了函数作用域就销毁了,找得到原来的常量字符串吗?找不到了,所以去打印的时候,一定是一系列的随机值。

    更改代码:

    1. #include
    2. #include
    3. #include
    4. void GetMemory(char ** p)
    5. {
    6. *p = "hello world";
    7. }
    8. void Test(void)
    9. {
    10. char* str = NULL;
    11. GetMemory(&str);
    12. printf(str);
    13. }
    14. int main()
    15. {
    16. Test();
    17. return 0;
    18. }

    2.3 题目三

    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. return 0;
    16. }

    解析:乍一看这个代码写的很好啊,但实际上,忘记释放动态开辟的内存空间了,需要及时释放。这题比较简单

    2.4 题目四

    1. #include
    2. #include
    3. #include
    4. void Test(void)
    5. {
    6. char* str = (char*)malloc(100);
    7. strcpy(str, "hello");
    8. free(str);
    9. if (str != NULL)
    10. {
    11. strcpy(str, "world");
    12. printf(str);
    13. }
    14. }
    15. int main()
    16. {
    17. Test();
    18. return 0;
    19. }

    解析:首先进入Test函数,str维护了一个动态内存开辟的100个字节的内存,调用strcpy函数把hello以及'\0'拷贝到这块空间里,释放str。到这一步都很完美,但是忘记置为空指针了,忘记了有个问题,就是str还是指向这块空间的,如果空间没人申请走,那么我指向的这块内存空间在某种程度上还能使用,如果运气好,可以打印出hello,但是如果申请走了,我再去使用这块空间是不是就是非法访问了。所以一定要记得释放后及时置空指针。

    总结

     上文就是动态内存常见的错误的详细讲解,下一节会给大家更新文件操作的方法。

    如果这份博客对大家有帮助,希望各位给小马一个大大的点赞鼓励一下,如果喜欢,请收藏一下,谢谢大家!!!
    制作不易,如果大家有什么疑问或给小马的意见,欢迎评论区留言。

  • 相关阅读:
    如何提高bp神经网络精度,bp神经网络收敛速度慢
    GBase 8s是否支持同城复制
    第八章 互联网上的音频视频服务 | 计算机网络(谢希仁 第八版)
    django特殊文件管理之Static和Media异同之处
    软件测试之Web安全测试详解
    为什么用IP访问网站也要使用SSL证书
    GIL全局解释器锁与协程
    详解Python文件: .py、.ipynb、.pyi、.pyc、​.pyd
    Layui快速入门之第六节 选项卡
    【批处理DOS-CMD命令-汇总和小结】-查看或修改文件属性(ATTRIB),查看、修改文件关联类型(assoc、ftype)
  • 原文地址:https://blog.csdn.net/s1mplewo/article/details/138204991