zoukankan      html  css  js  c++  java
  • C++内存分配与释放

    C++内存分配与释放

    1. new 运算符 与 operator new
    一条 new 表达式语句( new Type; )中的 new 是指 new 运算符.
    operator new 是定义在 #include <new> 中声明的一系列全局函数, 其中部分全局函数可被重写, 或在自定义类型定义为成员函数, 这样该类或其子类将使用成员函数的版本进行内存分配.
    new 和 operator 对应用程序至关重要, 一旦应用程序定义了全局版本的 operator new/delete, 应用程序就负担起了分配对态内存的职责, 必须保证这两个函数的完全正确.

    2. new 与 delete 运算符
    对 new Klass 表达式, 编译器执行3个过程:
    (1) 调用 operator new 或 operator new[] 函数分配一块足够大的,原始,未命名的内存以存储该类型的对象或对象的数组.
    编译器查找的顺序为:首先在类及其基类中查找,其次全局作用域内查找,如果没有找到使用标准库定义的版本.
    (2) 调用相应的构造函数, 构造这些对象, 并为其传入初始值.
    (3) 对象被分配了空间并构造完成后, 返回一个指向该对象或对象数组的指针.
    delete p 执行相反的过程:
    (1) 调用对象或对象数组的每一个元素调用析构函数.
    如果析构函数为虚函数(顶层基类析构函数为虚函数则该类析构函数也为虚函数), 调用对象实际类型的析构函数.
    如果为对象数组, 则从后往前调用析构函数.
    (2) 调用 operator delete 或 operator delete[] 函数释放内存.
    重写 new 与 delete 实际是重写了 operator new 和 operator delete 函数, 应用程序无法改变 new 和 delete 运算符的行为!
    显示的调用 operator 版本的函数与使用 new 或 delete 表达式形式, 形式无多大区别, 但他们之间的差异惊人, 需要仔细甄别.
    如果想把内存分配和对象构造分离开来, 可以使用 placement new 形式(或 allocator 类).
    char buf[100]; // 分配内存
    Object* p = new (buf) Object{1}; // 构造对象
    p->~Object(); // 析构对象

    3. 编译器定义的不同版本的 operator new/delete 函数的功能
    void* operator new(size_t) bad_alloc;
    void* operator new[](size_t) bad_alloc;
    void operator delete(void*) noexcept;
    void operator delete[](void*) noexcept;
    void* operator new(size_t, nothrow_t) noexcept;
    void* operator new[](size_t, nothrow_t) noexcept;
    void operator delete(void*, nothrow_t) noexcept;
    void operator delete[](void*, nothrow_t) noexcept;
    void* operator new(size_t, void* p) noexcept { return p; }
    void* operator new[](size_t, void* p) noexcept { return p; }
    void operator delete(void*, void*) noexcept {}
    void operator delete[](void*, void*) noexcept {}
    (1) operator new 默认版本在分配内存失败时抛出 bad_alloc, 其余的函数不抛出异常.
    (2)第(1)-(8)个函数可以重写.
    (3)第(9)-(12)函数的全局版本不能被重新定义(类成员版本无此限制), 实际上它们什么也不干, operator new 也只是简单的返回传入的地址(注意, 不分配内存).
    其中p指向的内存, 可以为任意地址, 包括栈上分配的内存, 只要其大小足够容纳对象. 这样应用程序就可以在预先分配的内存上构造对象.
    常见的 new 表达式使用形式约有不同, 如: Object* p = new (buf) Object{1}; // 以 buf 指定的地址构造一个 Object 对象, 其构造函数参数为 1.
    如果使用此方式, 构造了一个对象, 在内存销毁前, 需要手动调用对象的析构函数销毁对象, 如 p.~Object();
    (4)重写的 operator new 函数必须返回 void*, 并且第一个参数必须为 size_t 类型, 且该参数不能有默认实参. 重载的版本可以提供其它额外的参数.
    (5)为对象数组分配空间时使用 operator new[], 传入第一个参数为数组所有元素所需的空间.
    (6) operator new 在内存不足时会调用 new_handler 函数, 并要求其释放一部分内存, 只有在 new_handler 为空时才会抛出异常.
    可以调有标准库函数 set_new_handler 重新设置 new_handler 为自已定义的版本.
    (7)operator new 的 nothrow_t 版本并不能保证 new 不抛出异常. 举个例子:
    Object* p = new (nothrow) Object{}; 虽然指定了为 Object 对象分配内存时不抛出异常, 但 Object 在构造其成员对象的过程中, 如果内存不足, 仍然会抛出 bad_alloc.
    (8)如果调用 operator new 时, 要求分配的内存大小为 0 字节, 也会返回一个合法的地址.
    (9)delete 删除 nullptr 永远是安全的行为.
    (10)重写 operator new 时也要重写对应的版本的 operator delete.

    直接使用内存示例(仅用作演示函数功能, 实际项目中不要使用):
    const size_t BUF_SZ = 100;
    void* pbuf = operator new(BUF_SZ); // 分配100个字节的内存, 此时无对象, 当然也就不会调用构造函数
    memset(pbuf, '1', BUF_SZ);
    char* pc = static_cast<char*>(pbuf);
    pc[BUF_SZ - 1] = '';
    cout << "kao:" << pc << endl;
    operator delete(pbuf); // 释放内存, 不能直接使用 delete pbuf;

    4. 类成员函数 operator new/delete(数组版本同理,不赘述)
    (1) 重写这些函数与普通的 operator 系列函数(如 operator <)意义完全不同, 需要区别对待.
    (2) 类成员函数的 operator new/delete 必须为 static 函数, 并且可以不使用 static 声明.
    (3) 和其它成员函数一样, 受访问权限限定符限制, 例如 operator new 函数在类定义中声明为 private, 则该类及其子类都不能使用 new 分配对象.
    (4) 一旦一个类型中定义了一个 operator new 版本, 则需要同时实现其它 #include <new> 中声明的其它版本, 否则将不可以使用, 这和其它函数的重载类似.
    (5) 自定义类型中重载的 operator new 函数, 可以添加自定义参数. 但第一个参数必须为 size_t 类型, 并且返回类型必须为 void*.
    (6) 如果定义类型自已的 operator new 或 operator delete, 可以使用作用局运算符调用全局函数的版本. 如 ::new KlassA;
    (7) 当定义 operator delete 或 operator delete [] 时, 第二个形参可以为size_t类型的参数, 以提供第1形参所指对象的字节数.
    此形参用于删除继承体系中的对象, 如果其类对象中有一个虚函数, 其大小将为指针所指对象的动态类型的大小. (对单个对象调用仍使用 delete p;)

    5. 使用 allocator 类分配内存.
    allocator 类定义在头文件 memory 中, 使用 allocator 可将内存分配和构造过程分离开来.
    allocator 类是一个模板类, 可以在头文件中看到其完整定义. 它分配的内存是原始的, 未构造的, 标准库模板类采了此方法分配内存.
    主要的成员函数:
    allocator<t> alloc; // 定义一个名为 alloc 的 allocator 对象, 它可以为类型为 T 的对象分配内存
    T* allocate(size_t n); 分配一段原始,未构造的内存, 保存 n 个 T 类型的对象
    void construct(T* p, Args&&... args); args 被用来传递给构造函数, 用来在 p 指向的内存中构造一个对象
    void destroy(T* p); 析构一个对象, 即调用对对象 p 调用 T 的析构函数, p 必须是已构造的对象
    void deallocate(T* p, size_t n); p 必须是 allocate 返回的地址, 且 n 为 allocate 分配时指定的大小. 调用此函数之前, 应用程序必须确保对其中的每一个已初始化了的对象都调用了 destroy 函数
    未构造对象前使用对象, 其行为是未定义的. 当销毁一个对象后, 可以在不释放内存前重复使用该内存.

  • 相关阅读:
    php解析word,获得文档中的图片
    小程序 图表 antv f2 的使用
    eslint配置大全
    node 操作word excel
    vue-element-admin
    python中字符串前的r什么意思
    python3 三种字符串(无前缀,前缀u,前缀b)与encode()
    Markdown语法
    Python3 字符串前面加u,r,b的含义
    Python os.walk()
  • 原文地址:https://www.cnblogs.com/diysoul/p/8099914.html
Copyright © 2011-2022 走看看