在 C++ 中, 内存区分为5个区, 分别是: 堆, 栈, 自由存储区, 全局/静态存储区, 常量存储区.
malloc
new
从技术上来说, 堆(heap)是 C 语言和操作系统的术语, 它是操作系统所维护的一块特殊内存, 提供了动态分配的功能, 当运行程序调用 malloc() 时就会从堆中分配内存, 之后会调用 free() 将内存归还. 而自由存储区是 C++ 中通过 new 和 delete 动态分配和释放对象的 抽象概念 , 通过 new 来申请的内存区域成为自由存储区. 实际上, 基本所有的 C++ 编译器都默认使用堆来实现自由存储区 , 也就是说, 缺省的 全局运算符 new 和 delete 大多会通过 malloc 和 free 来实现, 这时候我们说 new 申请的内存在自由存储区上或者在堆中都是正确的. 但是程序员可以通过重载 new 和 delete 运算符, 来改用其他内存实现自由存储区, 这时候自由存储区就区别于堆了. 我们所需要记住的是: 堆是操作系统维护的一块特殊内存, 而自由存储区是 C++ 中通过 new 和 delete 动态分配和释放对象的抽象概念. 二者并不完全等价.
malloc()
free()
delete
free
(1). 申请的内存位置 new 操作符从 自由存储区 上为对象动态分配内存空间, 而 malloc 函数从 堆 上动态分配内存. 自由存储区是 C++ 基于 new 操作符的一个抽象概念, 凡是通过 new 操作符进行内存申请, 那么该内存就是自由存储区. 而堆是操作系统中的术语, 是操作系统所维护的一块特殊内存, 用于程序的内存动态分配, C 语言使用 malloc 从堆上分配内存, 使用 free 释放已经分配的对应内存. 自由存储区是否在堆上(问题等价于 new 申请的内存是否在堆上), 这取决于 new 运算符的实现细节. 自由存储区不仅可以是堆, 还可以是静态存储区, 这主要看 new 实现时在哪里为对象分配内存.
(2). 返回类型安全性 new 运算符内存分配成功时, 返回的是对象类型的指针, 类型严格与对象匹配, 无序进行类型转换, 故 new 是类型安全的运算符. 而 malloc 内存分配成功时返回的是 void * , 需要通过强制类型转换将 void * 指针转换成需要的类型.
void *
(3). 内存分配失败时的返回值 new 内存分配失败时, 会抛出 bad_alloc 异常; malloc 分配内存失败时会返回 NULL . 因此, 二者在判断是否分配成功时的代码逻辑不太, 如下所示:
bad_alloc
NULL
1234567891011121314
// mallocint* a = (int*)malloc(sizeof(int));if (a == NULL) { ...} else { ...}// newtry { int* a = new int();} catch (bad_alloc) { ...}
(4). 是否调用构造函数/析构函数 new 和 delete 会调用对象的构造函数/析构函数以完成对象的构造/析构. 而 malloc 则不会.
(5). 对数组的处理 C++ 提供了 new[] 和 delete[] 来专门处理数组类型. 但是对于 malloc 和 free 来说, 它并不关心分配的内存是否为数组, 需要程序员自行决定参数的合理设置.
new[]
delete[]
delete 会调用对象的析构函数, 和new对应
free 只会释放内存, 和malloc.
malloc/free是C++/C 语言的标准库函数, new/deletec是C++语言的运算符, 因此, 我们可以通过重载 new 和 delete 运算符, 来完成自定义的功能.
它们都可用于申请动态内存和释放内存, 对于非内部数据类型的对象而言, 光用 malloc/free 无法满足动态对象的要求, 对象在创建的同时要自动执行构造函数, 在消亡之时要自动执行析构函数, 由于malloc/free是库函数而不是运算符, 因此不在编译器控制权限之内, 不能够把执行构造函数和析构函数的任务强加于malloc/free. 因此C++语言需要一个能完成动态内存分配和初始化工作的运算符new, 以及一个能完成清理与释放内存工作的运算符delete.
运算符 new 和 new[] 实际上是分别调用了如下函数:
12
void * operator new(std::size_t);void * operator new[](std::size_t);
当我们在使用一个 new 的时候, 它就变成了一个表达式的形式, 如下所示:
1
Test* test = new Test;
new 表达式的行为主要有两步:
operator new()
delete只会调用一次析构函数, 而delete[]会调用数组内每一个成员的析构函数. 在More Effective C++中有更为详细的解释: 当delete操作符用于数组时, 它为每个数组元素调用析构函数, 然后调用operator delete来释放内存
在对 内建数据类型 使用时, delete和delete[]是等价的, 因此delete[]会调用数组元素的析构函数, 但是内部数据类型没有析构函数, 所以可以直接使用.
在C++中,类的对象建立分为两种,一种是静态建立,如 A a ;另一种是动态建立,如 A* ptr=new A ;这两种方式是有区别的。
A a
A* ptr=new A
(1). 只在对上分配类对象 就是不能静态建立类对象, 即不能直接调用类的构造函数. 首先要知道, 当对象建立在栈上面时, 是由编译器分配内存空间的, 当对象使用完以后, 编译器会调用析构函数来释放对象所占的空间. 实际上, 编译器在为类对象分配栈空间时, 会检查类的析构函数的访问性(其他非静态函数也会检查), 如果类的析构函数是私有的, 则编程器不会在栈空间上为类对象分配内存 . 因此, 我们只需要将析构函数设为私有, 类对象就无法建立在栈上了, 如下所示:
1234567
class A{public: A(){} void destroy(){delete this;}private: ~A(){}}
注意 , 由于 new 表达式会在分配内存以后调用构造函数, 因此构造函数必须是公有的, 同时, 由于 delete 此时无法访问私有的析构函数, 因此必须提供一个 destroy 函数, 来进行内存空间的释放.
destroy
存在问题:
virtual
private
protected
create
(2). 只在栈上分配类对象 只有使用new运算符,对象才会建立在堆上,因此,只要禁用new运算符就可以实现类对象只能建立在栈上。 虽然你不能影响new operator的能力(因为那是C++语言内建的),但是你可以利用一个事实:new operator 总是先调用 operator new,而后者我们是可以自行声明重写的。
因此,将operator new()设为私有即可禁止对象被new在堆上。
代码如下:
123456789
class A { private: void* operator new(size_t t){} // 注意函数的第一个参数和返回值都是固定的 void operator delete(void* ptr){} // 重载了new就需要重载delete public: A(){} ~A(){} };