之前有总结过内存管理,参看:C语言再学习 -- 内存管理

但现在看来,缺少示例。从新再写一篇文章,着重介绍常见内存错误、跨函数使用存储区。开始吧,再论内存管理!!

发生内存错误是件非常麻烦的事情。编译器不能自动发现这些错误,通常是在程序运行时才能捕捉到。而这些错误大多没有明显的症状时隐时现增加了改错的难度。

参看:《高质量C++ C编程指南》.林锐

一、常见的内存错误及其对策

1、内存分配未成功,却使用了它。

编程新手常犯这种错误,因为他们没有意识到内存分配会不成功。常用解决办法是,在使用内存之前检查指针是否为NULL。如果指针 p 是函数的参数,那么在函数的入口处用 assert (p != NULL) 进行检查。如果是用 malloc 或 new 来申请内存,应该用 if (p == NULL) 或者 if (p != NULL) 进行防错处理。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>int main (void)
{char *p = (char*)malloc (20 * sizeof (char));if (p == NULL) //必须检查是否分配成功{perror ("error"),exit (1);}strcpy (p, "hello world");puts (p);free (p);p = NULL;return 0;
}
输出结果:
hello world

2、内存分配虽然成功,但是尚未初始化就引用它

犯这种错误主要有两个原因:一是没有初始化的观念,二是误以为内存的缺省初值全为零,导致引用初值错误(例如数组)。

内存的缺省初值究竟是什么并没有统一的标准,尽管有些时候 为零值,我们宁可信其无不可信其有。所以无论用何种方式创建数组,都别忘了赋初值,即便是赋零值也不可省略,不要嫌麻烦。

#include <stdio.h>
#include <string.h>int main(void)
{int buffer[5] = {1, 2, 3, 4, 5}, i;memset(buffer, 0, sizeof(buffer));//将buffer元素全置为0for (i = 0; i < 5; ++i){printf ("%d ", buffer[i]);}printf ("\n");return 0;
}
输出结果:
0 0 0 0 0

3、忘记了释放内存,造成内存泄漏

含有这种错误的函数每被调用一次就丢失一块内存。刚开始时系统的内存充足,你看不到错误。终有一次程序突然死掉,系统出现提示:内存耗尽

这里还需要了解如何检查内存泄漏,参看:百度百科 -- 内存泄漏

动态内存的申请与释放必须配对,程序中 malooc 与 free 的使用次数一定要相同,否则肯定有错误 (new/delete同理)。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>int main (void)
{while (1) {char *p = (char*)malloc (10000000 * sizeof (char));if (p == NULL) //必须检查是否分配成功{perror ("error"),exit (1);}strcpy (p, "hello world");puts (p);}
//  free (p);
//  p = NULL;return 0;
}
输出结果:
hello world
hello world
。。。
hello world
hello world
error: Cannot allocate memory

4、释放了内存却继续使用它

有三种情况:

(1)程序中的对象调用关系过于复杂,实在难以搞清楚某个对象究竟是否已经释放了内存,此时应该重新设计数据结构,从根本上解决对象管理的混乱局面。

(2)函数的 return 语句写错了,注意不要返回指向栈内存“栈内存”的“指针”或者“引用”,因为该内存在函数体结束时自动销毁。

(3)使用 free 或 delete 释放了内存后,没有将指针设置为 NULL。导致产生“野指针”

#include <stdio.h>
#include <stdlib.h>
#include <string.h>int main (void)
{char *p = (char*)malloc (20 * sizeof (char));if (p == NULL) //必须检查是否分配成功{perror ("error"),exit (1);}free (p);strcpy (p, "hello world");puts (p);free (p); //释放了两次p = NULL;return 0;
}
输出结果:
hello world
*** glibc detected *** ./a.out: double free or corruption (fasttop): 0x0828b008 ***
段错误 (核心已转储)

总结:

函数用法:

type *p;
p = (type*)malloc(n * sizeof(type));
if(NULL == p)
/*请使用if来判断,这是有必要的*/
{
    perror("error...");
    exit(1);
}
.../*其它代码*/
free(p);
p = NULL;/*请加上这句*/


函数使用需要注意的地方:

1、malloc 函数返回的是 void * 类型,必须通过 (type *) 来将强制类型转换

2、malloc 函数的实参为 sizeof(type),用于指明一个整型数据需要的大小。

3、申请内存空间后,必须检查是否分配成功

4、当不需要再使用申请的内存时,记得释放,而且只能释放一次。如果把指针作为参数调用free函数释放,则函数结束后指针成为野指针(如果一个指针既没有捆绑过也没有记录空地址则称为野指针),所以释放后应该把指向这块内存的指针指向NULL,防止程序后面不小心使用了它。

5、要求malloc和free符合一夫一妻制,如果申请后不释放就是内存泄漏,如果无故释放那就是什么也没做。释放只能一次,如果释放两次及两次以上会出现错误(释放空指针例外,释放空指针其实也等于啥也没做,所以释放空指针释放多少次都没有问题)。

二、跨函数使用存储区

之前培训的时候有讲到,但是看书总结的时候,大多分开来讲了。现在来详细讲解下:

跨函数使用的三种情况:

(1)被调用函数可以使用调用函数的存储区,指针作形参可以让被调用函数使用其他函数的存储区

#include <stdio.h>
char* fa(char* p_str)
{char* p=p_str;p="hello world";return p;
}int main()
{char* str=NULL;printf("%s\n",fa(str));return 0;
}

(2)动态分配内存也可以实现跨函数使用存储区,只要动态分配函数内存没有被释放就可以被任何函数使用。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void foo (char *p)
{strcpy (p, "hello");puts (p);
}int main (void)
{char *p = (char*)malloc (20 * sizeof (char));strcpy (p, "hello world");if (p == NULL){return ;}strcpy (p, "hello world");puts (p);foo (p);free (p);p = NULL;return 0;
}
输出结果:
hello world
hello

(3)static 静态局部变量的存储区可以被任意使用

#include <stdio.h>
int i = 20;  //全局变量
void func (void)
{static int i = 10;  //静态/局部变量i++;printf("%d\n",i);
}
int main (void)
{func ();               //11  优先使用局部变量printf ("%d\n",i);    //20 func ();              //12   静态局部变量跨函数使用存储区    return 0;
}
输出结果:
11
20
12

三、讨论malloc(0)返回值

参看:关于malloc(0)的返回值问题

下面的代码片段的输出是什么,为什么?
char *ptr;
if ((ptr = (char *)malloc(0)) == NULL) 
puts("Got a null pointer");
else
puts("Got a valid pointer");

讨论  malloc(0)#include <stdio.h>
#include <stdlib.h>int main (void)
{char *ptr;if ((ptr = (char *)malloc(0)) == NULL) puts("Got a null pointer");elseputs("Got a valid pointer"); return 0;
}
输出结果:
Got a valid pointer

man malloc 查看:

The  malloc()  function allocates size bytes and returns a pointer to the allocated memory.  The memory is not initialized.  If size is 0, then malloc() returns either NULL, or a unique pointer value that can later be successfully passed to free().

翻译就是,就是传个0的话,返回值要么是NULL,要么是一个可以被free调用的唯一的指针。

用一个测试示例来说明:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <malloc.h>int alloc_memory(char *p , int size)
{printf("\nbefore malloc %p\n",p);p = (char *)malloc(size);if(!p){printf("malloc error  \n");return -1;}//len of malloc(0)printf("len of malloc(%d)  is  %d  ,the ture is %d\n",size,strlen(p),malloc_usable_size(p));//the first member printf("the first member of malloc(%d) is %p:%d \n",size,p,*p);//set the first member*p = 10;printf("set the first member of malloc(%d) is %p:%d \n",size,p,*p);//memcpymemset(p,'\0',12);memcpy(p,"01234567890123456789",12);printf("after memcpy , the content is %s   len is %d  , the ture is %d \n",p,strlen(p),malloc_usable_size(p));free(p);p = NULL;printf("\n");
}int main(int argc ,char **argv)
{int size = -1;char *p = NULL;//malloc(0)size = 0;alloc_memory(p,size);//malloc(5)size = 5;alloc_memory(p,size);//malloc(20)size = 20;alloc_memory(p,size);return 0;
}
输出结果:
before malloc (nil)
len of malloc(0)  is  0  ,the ture is 12
the first member of malloc(0) is 0x932f008:0
set the first member of malloc(0) is 0x932f008:10
after memcpy , the content is 012345678901   len is 15  , the ture is 12 before malloc (nil)
len of malloc(5)  is  0  ,the ture is 12
the first member of malloc(5) is 0x932f008:0
set the first member of malloc(5) is 0x932f008:10
after memcpy , the content is 012345678901   len is 15  , the ture is 12 before malloc (nil)
len of malloc(20)  is  0  ,the ture is 20
the first member of malloc(20) is 0x932f018:0
set the first member of malloc(20) is 0x932f018:10
after memcpy , the content is 012345678901   len is 12  , the ture is 20

从测试结果来看,可以得出以下几个结论:
1. malloc(0)在我的系统里是可以正常返回一个非NULL值的。这个从申请前打印的before malloc (nil)和申请后的地址0x9e78008可以看出来,返回了一个正常的地址
2. malloc(0)申请的空间到底有多大不是用strlen或者sizeof来看的,而是通过malloc_usable_size这个函数来看的。---当然这个函数并不能完全正确的反映出申请内存的范围。
3. malloc(0)申请的空间长度不是0,在我的系统里它是12,也就是你使用malloc申请内存空间的话,正常情况下系统会返回给你一个至少12B的空间。这个可以从malloc(0)和malloc(5)的返回值都是12,而malloc(20)的返回值是20得到。---其实,如果你真的调用了这个程序的话,会发现,这个12确实是”至少12“的。
4. malloc(0)申请的空间是可以被使用的。这个可以从*p = 10;及memcpy(p,"01234567890123456789",12);可以得出。

总结:为了安全起见,malloc(0)的非NULL返回值,最好不要进行除了free()之外的任何操作

C语言再学习 -- 再论内存管理相关推荐

  1. UNIX再学习 -- 死磕内存管理

    malloc/free简化实现:malloc 和 sbrk 关系:虚拟内存机制. 一个内存管理 C 语言部分讲,UNIX部分讲,Linux部分还讲,死磕到底!! 一.mallc/free简化实现 上篇 ...

  2. c语言基础学习08_关于内存管理的复习

    ============================================================================= 对于c语言来讲,内存管理是一个很重要的内容, ...

  3. 深度学习中的内存管理问题研究综述

    点击上方蓝字关注我们 深度学习中的内存管理问题研究综述 马玮良1,2, 彭轩1,2, 熊倩1,2, 石宣化1,2, 金海1,2 1 华中科技大学计算机科学与技术学院,湖北 武汉 430074 2 华中 ...

  4. Redis运维和开发学习笔记(7) 内存管理和过期策略

    Redis运维和开发学习笔记(7) 内存管理和过期策略 文章目录 Redis运维和开发学习笔记(7) 内存管理和过期策略 内存回收策略 惰性删除 定时任务删除 maxmemory 过期策略allkey ...

  5. C语言再学习 -- 再论数组和指针

    之前有总结指针数组,但是现在看来总结的太简单了.好多重要的知识点都是一带而过的.本想在后面添加后来想想算了,还是再写一篇文章来详细介绍数组和指针这对冤家吧. 之前总结的,参看:C语言再学习 -- 数组 ...

  6. 《C语言深度解剖》学习笔记之内存管理

    第5章 内存管理 1.野指针 定义指针变量的同时最好初始化为NULL,用完指针后也将变量的值设置为NULL.也就是说除了使用时,别的时间都把它设置为NULL 2.堆,栈和静态区 堆:由malloc系列 ...

  7. C语言提高篇之——动态内存管理

    目录 1. 动态内存分配存在的意义 2.动态内存函数介绍 2.1 malloc 2.2 free 2.3 calloc 2.4 realloc 3.常见的动态内存错误 3.1 对空指针解引用 3.2 ...

  8. 十年码农教你学习,linux内存管理——内存管理架构

    通常情况下,一个高级操作系统必须要给进程提供基本的.能够在任意时刻申请和释放任意大小内存的功能,就像malloc 函数那样,然而,实现malloc 函数并不简单,由于进程申请内存的大小是任意的,如果操 ...

  9. UNIX再学习 -- 再识

    到了Unix编程,因为之前有C语言的基础吧,看了下目录大部分内容之前都有所总结,进度应该能够加快.不过最近有点小郁闷,申请博客专家转正失败了,这也让我重新审视了自己更新博客的初心是什么.绝不是为了与自 ...

最新文章

  1. word关闭未响应_大众途观全景天窗遮阳卷帘无法关闭
  2. 计算机在材料科学中的应用电子版,计算机在材料科学中的应用技术
  3. 给apache安装mod_rewrite模块
  4. python安装成功的图标_安装Python
  5. 算法不会,尚能饭否之双向循环链表
  6. linux查询机器信息,linux_机器信息查询
  7. 知乎App有哪些非常“贴心”的UI设计
  8. 堆排序-java实现
  9. CxImage 初识
  10. 程序员一般可以从什么平台接私活?
  11. [论文写作] Wrong vs Mistake vs Error vs Incorrect vs Erroneous
  12. slim 搭建rnn_RNN入门(三)利用LSTM生成旅游点评
  13. 蒙特卡洛树搜索 Monte Carlo Tree Search
  14. VB打造QQ批量登陆器
  15. Kafka系列 —— 生产实践分享
  16. 使用卷积神经网络(普通CNN和改进型LeNet)以及数据增强和迁移学习技巧识别猫和狗,并制作成分类器软件(基于Keras)
  17. Java Web项目源代码|CRM客户关系管理系统项目实战(Struts2+Spring+Hibernate)解析+源代码+教程
  18. 从秋香,芳娜到不嫁国人的女大学生
  19. 标记语言/脚本语言/
  20. oracle安装时怎样调整sga,深入讲解调整Oracle SGA大小的解决方法

热门文章

  1. 我是一只IT小小鸟读书笔记3
  2. XCode调试器LLDB
  3. 00075_BigInteger
  4. Sprint第三阶段(第四天12.12)
  5. memcache运行机制(转)
  6. plsql连接oracle数据库
  7. 梯度下降法,牛顿法,高斯-牛顿迭代法,附代码实现
  8. Zero-Copysendfile浅析
  9. 坐标系旋转变换公式图解
  10. 科大星云诗社动态20210530