zoukankan      html  css  js  c++  java
  • 内存管理

    内存管理


    题外话

    最近在知乎看到叫markdown的编辑器

    据说它很神奇,并且它比html还简单,想知道如何在博客园用markdown写博客,请点击

    想看关于markdown的中文语法说明,请点击

    想知道markdown的入门指导,请点击;也可以下载一个markdown编辑器,查看软件的帮助

    好,言归正传

    本文是笔者学习《高质量C++C 编程指南》 (林锐) 的一些总结。

    内存分配方式

    内存分配方式有三种:

    • 从静态存储区分配。内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在。例如全局变量、static变量
    • 从栈上创建。在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元被自动释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内容容量很有限。想起了 Stackoverflow 名字的由来。
    • 从堆上分配,也称为动态内存分配。程序在运行的时候用malloc或者new申请任意大小的内存,程序员自己负责在何时用free或delete释放内存。动态内存的生存期由我们决定,使用非常灵活,但容易出现内存泄漏等问题。

    常见的内存错误及对策

    发生内存错误很麻烦,编辑器不能自动发现这些错误,可能在程序运行的时候被调试人员发现。下面列举一些常见的内存错误以及对策:

    • 内存分配未成功,却使用了它

    在使用之前检验指针是否为NULL。如果使用malloc或new来申请内存。应该使用如下的方法进行防错检验

    if(p==NULL)||if(p!=NULL)
    
    • 内存分配成功,但没有初始化就引用了它

    这种情况下就要加强初始化的观念,不要误认为内存的缺省初值全为零,导致引用初值错误。

    • 内存分配成果并且已经初始化,但操作越过了内存的边界

    通常会出现在for循环中,笔者曾经在用迭代器的时候因为操作不当而导致指针越界

    • 忘记了释放内存却继续使用它

    有三种情况:

    1. 杂乱的数据结构,致使不能从根本上解决对象管理的混乱局面。
    2. 函数的return语句写错了,不要返回指向“栈内存”的“指针”或者“引用”,因为该内存会在函数题结束时被自动销毁。下文也会有这种类型错误的很典型的函数,具体请见下文。
    3. 使用free或者delete释放了内存后,没有将指针设置为NULL。导致产生野指针

    指针与数组的对比

    在C++/C的程序中,指针和数组在不少地方可以相互替换着用,让人产生了一种错觉,以为两者是等价的。

    首先,数组要么在静态存储区被创建(如全局数组),要么在栈上被创建。数组名对应着(和不是指向)一块内存,其地址与容量在生命期内保持不变,只有数组的内容可以改变。

    指针可以随时指向任意类型的内存块,它的特征是"可变",所以我们常用指针来操作动态内存。指针远比数组灵活,但也很危险,使用的时候要考虑上文中所提到的内容。

    下面以字符串为例比较指针和数组的特性

    修改内容

    char a[] = "hello";
    a[0] = 'X';
    cout << a << endl;
    char *p = "world";	//注意p指向常量字符串
    p[0] = 'X';			//编译器并不能发现该错误
    cout << p << endl;
    

    上述代码中,字符数组a的容量是6个字符,分别为hello,a的内容可以改变,如a[0] = 'X' 。 指针p指向常量字符串“world”(位于静态存储区,内容为world),常量字符串的内容是不可以被修改的。从语法上看,编译器不会发现语句p[0] = 'X' 有什么不妥,但是该语句企图修改常量字符串的内容而导致运行错误。

    内容复制与比较

    不能对数组名进行直接的复制与比较,例如下面的代码:

    char a[] = "hello";
    char b[10];
    strcpy(b,a);
    if(strcmp(b,a)==0){
    //……指针操作
    }
    int len = strlen(a);
    char *p = (char*)malloc(sizeof(char)*(len+1);
    strcpy(p,a);
    

    如果想把数组a的内容复制给数组b,不能用语句b = a,否则会出现编译错误。应该用标准库函数strcpy进行复制。同理,语句if(p==a)比较的不是内容而是地址,应该用库函数strcmp来比较。

    计算内存容量

    用运算符sizeof可以计算出数组的容量(字节数)。见下面的程序:

    char a[] = "hello wolrd";
    char *p = a;
    cout << sizeof(a) << endl;  //12字节
    cout << sizeof(p) << endl;  //4字节
    void func(char a[100]){
    	cout << sizeof(a) << endl; //4字节 而不是100字节
    }
    

    可以看出来,sizeof(a)的值是12(包括'')。指针p指向a,但是sizeof(p)的值却是4。这是因为sizeof(p)得到的是一个指针变量的字节数,=>sizeof(char*),而不是p所指的内存容量。

    C++/C语言没有办法知道指针所指的内存容量,除非特地在申请内存时去记住它。

    还有一点值得注意的是,当数组作为函数的参数进行传递时,该数组自动退化为同类型的指针,所以说无论在上面的func(char a[SIZE])中,SIZE值为多少,sizeof(a)始终等于sizeof(char*)。这就是数组退化为指针的一种特例。

    指针参数是如何传递内存的?

    如果一个函数的参数是一个指针,不要指望用该指针去申请动态内存。请看如下的这个函数:

    void getMemory(char *p, int num)
    {
    	p = (char *)malloc(sizeof(char)* num);
    }
    
    void Test(void)
    {
    	char *str = NULL;
    	getMemory(str, 100);	//str仍然为NULL
    	strcpy(str,"hello");	//运行错误
    }
    

    上面想用getMemory(str,100)并没有使得str获得期望的内存,str依旧是NULL。

    毛病出在getMemory中。编译器总是要为每个参数制作临时副本,指针p的副本是_p,编译器使_p=p。如果函数体内的程序修改了_p的内容,就导致参数p的内容作相应的修改。这就是指针可以用作输出参数的原因。在上例中,_p申请了新的内存,只是把_p所指的内存地址改变了,但是p丝毫没变。所以函数getMemory并不能输出任何东西。事实上,每执行一次getMemory就会泄漏一块内存,因为没有使用free释放内存。

    函数改进

    如果非得要用指针参数去申请内存,那么应该改用“指向指针的指针”,见下述函数:

    void getMemory2(chaar **p, int num)
    {
    	*p = (char *)malloc(sizeof(char) * num);
    }
    
    void Test2(void)
    {
    	char *str = NULL;
    	getMemory2(&str,100);	//注意参数是&str,而不是str
    	strcpy(str,"hello");
    	cout << str << endl;
    	free(str);
    }
    
    
    

    如果不能很好的理解“指向指针的指针”,我们也可以用函数的返回值来传递动态参数。这个方法更加简单。

    char * getMemory3(int num)
    {
    	char *p = (char *)malloc(sizeof(char) * num);
    	return p;
    }
    
    void Test3(void)
    {
    	char *str = NULL;
    	str = getMemory3(100);
    	strcpy(str,"hello");
    	cout << str << endl;
    	free(str);
    }
    

    用函数返回值来传递动态内存这种方法虽然好用,但是有人会把return语句用错了。这里强调不要用return语句返回指向"栈内存"的指针,因为该内存在函数结束时自动消亡。见下述代码:

    char * getString(void)
    {
    	char p[] = "hello world";
    	return p;		//编译器将提出警告
    }
    
    void Test4(void)
    {
    	char *str = NULL;
    	str = getString();		//str的内容是垃圾
    	cout << str << endl;
    }
    

    用Debug逐步跟踪Test4,发现执行str = getString语句后str不再是NULL指针,但是str的内容不是"hello world"而是垃圾。

    杜绝“野指针”

    在C++/C编程中,我们会发现有一些"似是而非"的特征:

    • 指针消亡了,并不代表它所指的内存会被自动释放。
    • 内存被释放了,并不代表着指针消亡或成了NULL指针。

    如果程序终止了运行,一切指针都会消亡,动态内存会被操作系统回收。所以,如果想让程序长期地运作,我们就要及时手动释放内存,并且将指针设置为NULL。

    下面我们来说一说“野指针”,它不是NULL指针,是指向“垃圾”内存的指针,“野指针”很危险。if语句对它不起作用。
    “野指针”的成因主要有两种:

    • 指针变量没有被初始化。任何指针变量刚被创建时不会自动成为NULL指针,它的缺省值是随机的,它会乱指一起。所以,指针变量在创建的同时应当被初始化,要么将指针设置为NULL,要么让它指向合法的内存。例如:
    char *p = NULL;
    or
    char *str = (char *)malloc(100);
    
    • 指针p被free或者delete之后,没有置为NULL,让人误以为p是个合法的指针。

    以上内容为笔者根据林锐博士的《高质量C++C 编程指南》的一些摘录以及一些自己的拓展。

    欢迎转载,转载请注明出处

    update by 2017/3/27 13:58

    已完结

    by一枝猪

  • 相关阅读:
    unexpected inconsistency;run fsck manually esxi断电后虚拟机启动故障
    centos 安装mysql 5.7
    centos 7 卸载mysql
    centos7 在线安装mysql5.6,客户端远程连接mysql
    ubuntu 14.04配置ip和dns
    centos7 上搭建mqtt服务
    windows eclipse IDE打开当前类所在文件路径
    git 在非空文件夹clone新项目
    eclipse中java build path下 allow output folders for source folders 无法勾选,该如何解决 eclipse中java build path下 allow output folders for source folders 无法勾选,
    Eclipse Kepler中配置JadClipse
  • 原文地址:https://www.cnblogs.com/chunzhulovefeiyue/p/6619139.html
Copyright © 2011-2022 走看看