zoukankan      html  css  js  c++  java
  • (转)c/c++ 内存管理

    1、malloc()

    malloc()函数用来分配内存:将总共需要的字节数作为参数传递给该函数,返回值是指向最新分配的内存的指针,而如果内存没有分配好,则返回值是NULL。

    malloc()的使用技术:

    some_type *pointer;

    pointer = malloc(count * sizeof(*pointer));

    注:

    (1)这个方法保证malloc()会分配正确数量的内存,而不用考虑pointer的生命。如果pointer的类型后来变了,sizeof算子自动确保要分配的字节数仍然正确。

    (2)malloc()返回的内存是“没有“初始化的。这块内存可能包含任何随机的垃圾,你可以马上用有效数据或者至少是用零来初始化这块内存。要用0初始化,可以用

    void *memset(void *s, int c, size_t n);

    (3)malloc()最终通过缺页异常获取的物理内存中的原有数据,大多数情况下是0(但不能保证一定是0)

    2、calloc()

    calloc()函数是malloc的简单包装。它的主要优点是把动态分配的内存清零。

    void *calloc(size_t nmemb, size_t size);

    有经验的程序员更喜欢使用calloc(),因为这样的话新分配内存的内容就不会有什么问题,调用calloc()肯定会清0并且可以避免调用memset().

    3、realloc()

    函数原型:extern void *realloc(void *mem_address, unsigned int newsize);#include <stdlib.h> 有些编译器需要#include <alloc.h>。改变mem_address所指内存区域的大小为newsize长度。如果重新分配成功则返回指向被分配内存的指针,否则返回空指针NULL。当内存不再使用时,应使用free()函数将内存块释放。

    注意:这里原始内存中的数据还是保持不变的。

    4、malloc()和calloc()

    函数malloc()和calloc()都可以用来动态分配内存空间,但两者稍有区别:

    (a)malloc()函数有一个参数,即要分配的内存空间的大小:

    void *malloc(size_t size);

    calloc()函数有两个参数,分别为元素的数目和每个元素的大小,这两个参数的乘积就是要分配的内存空间的大小。

    void *calloc(size_t numElements,size_t sizeOfElement);

    (b)如果调用成功,函数malloc()和函数calloc()都将返回所分配的内存空间的首地址。

    (c)函数malloc()和函数calloc()的主要区别是前者不能初始化所分配的内存空间,而后者能

    (d)如果由malloc()函数分配的内存空间原来没有被使用过,则其中的每一位可能都是0;反之,如果这部分内存曾经被分配过,则其中可能遗留有各种各样的数据。也就是说,使用malloc()函数的程序开始时(内存空间还没有被重新分配)能正常进行,但经过一段时间(内存空间还已经被重新分配)可能会出现问题。

    (e)函数calloc()会将所分配的内存空间中的每一位都初始化为零,也就是说,如果你是为字符类型或整数类型的元素分配内存,那麽这些元素将保证会被初始化为0;如果你是为指针类型的元素分配内存,那麽这些元素通常会被初始化为空指针;如果你为实型数据分配内存,则这些元素会被初始化为浮点型的零。

    (f)calloc(m, n) 本质上等价于 

    p = malloc(m * n); 

    memset(p, 0, m * n);

    5、free和delete

    (a)它们只是把指针所指的内存给释放掉,但并没有把指针本身干掉。

    (b)p被free以后其地址仍然不变(非NULL),只是该地址对应的内存是垃圾,p成了“野指针”。如果此时不把p设置为NULL,会让人误以为p是个合法的指针。

    (c)我们有时记不住p所指的内存是否已经被释放,在继续使用p之前,通常会用语句if (p != NULL)进行防错处理。但是只有当p = NULL时if (p != NULL)才起作用。   

    6、new和malloc 

    (1)、new 是c++中的操作符,malloc是c 中的一个函数.

    (2)、new 不止是分配内存,而且会调用类的构造函数,同理delete会调用类的析构函数,而malloc则只分配内存,不会进行初始化类成员的工作,同样free也不会调用析构函数. 

    (3)、内存泄漏对于malloc或者new都可以检查出来的,区别在于new可以指明是那个文件的那一行,而malloc没有这些信息。

    (4)、new 和 malloc效率比较 

    (a)new可以认为是malloc加构造函数的执行。

    (b)new出来的指针是直接带类型信息的 而malloc返回的都是void指针。

    7、new/ delete和malloc/free

    (a)New/ delete 是运算符,malloc/free是函数

    (b)malloc与free是C++/C语言的标准库函数,new/delete是C++的运算符。它们都可用于申请动态内存和释放内存。

    (c)对于非内部数据类型的对象而言,光用maloc/free无法满足动态对象的要求。对象在创建的同时要自动执行构造函数,对象在消亡之前要自动执行析构函 数。由于malloc/free是库函数而不是运算符,不在编译器控制权限之内,不能够把执行构造函数和析构函数的任务强加于malloc/free。

    (d)不要企图用malloc/free来完成动态对象的内存管理,应该用new/delete。由于内部数据类型的“对象”没有构造与析构的过程,对它们而言malloc/free和new/delete是等价的。

    (e)C++语言需要一个能完成动态内存分配和初始化工作的运算符new,以及一个能完成清理与释放内存工作的运算符delete。注意new/delete不是库函数.

    (f)new delete在实现上其实调用了malloc,free函数。new operator除了分配内存,还要调用构造函数,malloc函数只是负责分配内存。 

    (g)既然new/delete的功能完全覆盖了malloc/free,为什么C++不把malloc/free淘汰出局呢?这是因为C++程序经常要调用C函数,而C程序只能用malloc/free管理动态内存。如果用free释放“new创建的动态对象”,那么该对象因无法执行析构函数而可能导致程序出错。如果用delete释放“malloc申请的动态内存”, 理论上讲程序不会出错,但是该程序的可读性很差。所以new/delete必须配对使用,malloc/free也一样。

    8、malloc/free 的使用要点

    (a)malloc只关心两个要素:“类型转换”和“sizeof”

    (b)malloc函数本身并不识别要申请的内存是什么类型,它只关心内存的总字节数.

    (c)malloc返回值的类型是void *,所以在调用malloc时要显式地进行类型转换,将void * 转换成所需要的指针类型。

    (d)语句free(p)之所以能正确地释放内存.是因为指针p的类型以及它所指的内存的容量事先都是知道的。如果p是NULL指针,那么free对p无论操作多少次都不会出问题。如果p不是NULL指针,那么free对p连续操作两次就会导致程序运行错误。

    9、new/delete 的使用要点

    (a)运算符new使用起来要比函数malloc简单得多,这是因为new内置了sizeof、类型转换和类型安全检查功能。对于非内部数据类型的对象而言,new在创建动态对象的同时完成了初始化工作。如果对象有多个构造函数,那么new的语句也可以有多种形式。

       例如:

       int  *p1 = (int *)malloc(sizeof(int) * length);

       int  *p2 = new int[length];

    (b)如果用new创建对象数组,那么只能使用对象的无参数构造函数。

    Obj  *objects = new Obj[100]; // 创建100个动态对象

    (c)在用delete释放对象数组时,留意不要丢了符号“[]”。delete []objects; // 正确的用法

    10、野指针

    ——“野指针”不是NULL指针,是指向“垃圾”内存的指针。人们一般不会错用NULL指针,因为用if语句很容易判断。但是“野指针”是很危险的,if语句对它不起作用。

    “野指针”的成因主要有两种:

    (a)指针变量没有被初始化。任何指针变量刚被创建时不会自动成为NULL指针,它的缺省值是随机的,它会乱指一气。所以,指针变量在创建的同时应当被初始化,要么将指针设置为NULL,要么让它指向合法的内存。

    (b)指针p被free或者delete之后,没有置为NULL,让人误以为p是个合法的指针.

    (c)指针操作超越了变量的作用范围。这种情况让人防不胜防,

    11、内存分配方式

    内存分配方式有三种:

    (a)从静态存储区域分配。内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在。例如全局变量,static变量。

    (b)在栈上创建。在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。

    (c)从堆上分配,亦称动态内存分配。程序在运行的时候用malloc或new申请任意多少的内存,程序员自己负责在何时用free或delete释放内存。动态内存的生存期由我们决定,使用非常灵活,但问题也最多。

    **常见的内存错误及其对策

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

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

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

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

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

    3、 内存分配成功并且已经初始化,但操作越过了内存的边界。

    例如在使用数组时经常发生下标“多1”或者“少1”的操作。特别是在for循环语句中,循环次数很容易搞错,导致数组操作越界。

    4、 忘记了释放内存,造成内存泄露。

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

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

    5、 释放了内存却继续使用它。(有三种情况:)

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

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

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

    **改正注意事项:

    (a)用malloc或new申请内存之后,应该立即检查指针值是否为NULL。防止使用指针值为NULL的内存。

    (b)不要忘记为数组和动态内存赋初值。防止将未被初始化的内存作为右值使用。

    (c)避免数组或指针的下标越界,特别要当心发生“多1”或者“少1”操作。

    (d)动态内存的申请与释放必须配对,防止内存泄漏。

    (f)用free或delete释放了内存之后,立即将指针设置为NULL,防止产生“野指针”。

     

    12、指针与数组的对比

    (a)指针和数组在不少地方可以相互替换着用,但两者不是等价的!

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

    (c)指针可以随时指向任意类型的内存块,它的特征是“可变”,所以我们常用指针来操作动态内存。指针远比数组灵活,但也更危险。

    (d)若想把数组a的内容复制给数组b,不能用语句 b = a ,应该用标准库函数strcpy进行复制

    (f)比较b和a的内容是否相同,不能用if(b==a) 来判断,应该用标准库函数strcmp进行比较

    (g)语句p = a 不是把a的内容复制指针p,而是把a的地址赋给了p。可以先用库函数malloc为p申请一块容量为strlen(a)+1个字符的内存,再用strcpy进行字符串复制。

    (h)语句if(p==a) 比较的不是内容而是地址,应该用库函数strcmp来比较。

    (i)用运算符sizeof可以计算出数组的容量(字节数)(注意别忘了’\0’)。

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

    (k)注意当数组作为函数的参数进行传递时,该数组自动退化为同类型的指针。(即在函数内的用数组名的本质是相对应的指针)。

    13、指针参数的传递内存

    (a)如果函数的参数是一个指针,不能用该指针去申请动态内存。

    (b)如果非得要用指针参数去申请内存,那么应该改用“指向指针的指针”.

    复制代码
    void GetMemory2(char **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);
    }
    复制代码

    (c)由于“指向指针的指针”这个概念不容易理解,我们可以用函数返回值来传递动态内存。这种方法更加简单。

    复制代码
    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);
    }
    复制代码

    14、处理内存耗尽问题

    如果在申请动态内存时找不到足够大的内存块,malloc和new将返回NULL指针,宣告内存申请失败。通常有三种方式处理“内存耗尽”问题

    (a)判断指针是否为NULL,如果是则马上用return语句终止本函数。

    复制代码
    void Func(void)
    {
    A  *a = new A;
    if(a == NULL)
    {
           return;
     }
    …
    }
    复制代码

    (2)判断指针是否为NULL,如果是则马上用exit(1)终止整个程序的运行。例如:

    复制代码
    void Func(void)
    {
    A  *a = new A;
    if(a == NULL)
    {
    cout << “Memory Exhausted” << endl;
             exit(1);
    }
    …
    }
    复制代码

    (3)为new和malloc设置异常处理函数。例如Visual C++可以用_set_new_hander函数为new设置用户自己定义的异常处理函数,也可以让malloc享用与new相同的异常处理函数。详细内容请参考C++使用手册。

    **(1)(2)方式使用最普遍。如果一个函数内有多处需要申请动态内存,那么方式(1)就显得力不从心(释放内存很麻烦),应该用方式(2)来处理。

  • 相关阅读:
    POJ 1795 DNA Laboratory
    CodeForces 303B Rectangle Puzzle II
    HDU 2197 本源串
    HDU 5965 扫雷
    POJ 3099 Go Go Gorelians
    CodeForces 762D Maximum path
    CodeForces 731C Socks
    HDU 1231 最大连续子序列
    HDU 5650 so easy
    大话接口隐私与安全 转载
  • 原文地址:https://www.cnblogs.com/wenxp2006/p/2554832.html
Copyright © 2011-2022 走看看