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一枝猪

  • 相关阅读:
    php原生代码实现explode函数功能
    PHP的命名空间
    高性能索引策略二
    .Net Core缓存组件(Redis)源码解析
    .Net Core缓存组件(MemoryCache)源码解析
    Docker系列之Docker容器(读书笔记)
    Docker系列之Docker镜像(读书笔记)
    .Net Core中的日志组件(Logging)
    .Net Core Cors中间件解析
    .Net Core项目添加日志功能
  • 原文地址:https://www.cnblogs.com/chunzhulovefeiyue/p/6619139.html
Copyright © 2011-2022 走看看