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] = '\0'; 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 函数 未构造对象前使用对象, 其行为是未定义的. 当销毁一个对象后, 可以在不释放内存前重复使用该内存.
|
请发表评论