在介绍自动手功内存管理之前,我们先介绍一下自动内存管理。
自动内存管理与局部变量相关,当程序执行时看到变量定义时,一个局部变量就开始占用系统内存了。当程序运行出定义局部变量的语句快时系统会自动释放局部变量所占的内存,事实上局部变量占用的是栈空间。
向下面的例子一样,程序员经常犯的一个错误就是返回一个无效的指针。一个内存释放后变量立即变的无效了。
int * badPointer() {
int i;
return &i;
}
badPointer函数返回本地变量i.然而,事实上当这个函数返回变量i时,编译器已经将i所占用内存释放。所以&i不再合法,如果我们坚持这样做只能将变量i定义成static:
int * pointerToStatic() {
static i;
return &i;
}
因为i是static其占用的是静态区的内存,在程序退出之前内存不会被释放。
内存,Cache,寄存器
通常计算机将数据存放在物理内存,cache及寄存器中。与其它两个数据存储方式相比,内存是比较大的。每个内存单元是通过内存地址来访问的并且内存单元是必须是联系的。不同的体系架构内存管理方式不同,在一些系统架构中部分内存用业访问物理设备(这种方式叫做内存映射I/O)。
Cache 是一个小型的内存,一级Cache直接存放在CPU中,或是二级Cache存放在主板上。Cache存放内存中最近使用过的一个拷贝,使其加载速度更快。因为Cache被硬件隐藏我们通常不用担心Cache除非我们修改内核。
寄存器单元位于CPU内部所以访问速度非常快.寄存器的访问速度比内存快的多,通常用来存放比较小的计算需要的数据,如:函数局部变量或算术运算的中间值。如果register关键字来定义一个局变量,这个局部变量直接被编译器放到寄存器中,而不是内存单元中。但现在编译器会自动优化来决定哪个变量放在寄存器中。
手功内存管理
当我们谈到内存管理时,恰当的进行内存释放是内存管理的关键。
从自由存储区中审请一个新的对象,C语言用malloc函数而c++用new 操作符。什么时候进行对象创建是一个业务逻辑中琐碎的事情。关键问题是什么时候对对象不在需要并将其释放到自由堆中。
内存管理可能产生的问题
内存泄露
内存泄露发生在当程序运行过程中审请了内存但没有在其不需要的时候进行释放。如果内存泄露的非常多,它将消耗所有的内存资源,因为系统需要进行页交换所以最终会使系统变的非常慢,最后我们得到内存不够的错误。查询内存泄露问题是非常艰难的,因为没有清楚的代码错误。
缓存溢出
缓存溢出是在向内存中写数据进超出了内存申请时的边界值,我们也可以称其为数据损坏(data corruption)。这种情况是非常严重的,因为在写数据越界之前这种情况是不会发生的,这种情况可能在偶而发生,当其发生时我们的程序变的非常奇怪。因为内存中我有一个错误的值。
内存没有初始化
既然c/c++允许我们创建没有初始化的变量,我们就可能读到一个没有初始化的数据。通过malloc()函数或new 操作符审请的内存没有初始化。
错误的内存管理
这种错误可能发生在对同一块内存free()了多次或是一个内存已经被释放了还进行访问,或是free()一块从来没有审请的内存块。这些问题也可能发生在用delete来代替delete[],或是错误的内存访问组合:malloc 与delete或 new 与 free
New Operator and Operator New
new operator 与 operator new 有什么区别呢?
看看下面一行代码:
string *ptrStr = new string("Where is my place in Memory?");
其实new 就是new 操作符.new 是c++的内部操作符,我们不能改变这个操作符的行为。下面两部分是是描述其做什么。
1. 它根据对象请求的类型保持足够的内存.上面的例子中它审请了足够的内存去保持string对象。
2. 调度其构造函数来初始化审请的对象。
在c++中内存审请与对象的初始化是在一起的。当我们使用new审请一块内存,其对象也在刚才审请的那块内存中初始化。也就是说new操作符经常做两件事情,我们不能修改其行为。
当我们处理内存审请时,我们必需处理两件事情(审请与初始化)。我们能够改变的是怎么为一个对象审请一块内存.new 操作符调用一个函数去执行审请需要的内存操作,我们可以重写或重载这个函数的操作来改变其操作行为.
new 操作符调用的是什么操作函数呢?是 operator new
void * operator new(size_t size);
返回类型为void*.然而这个操作返回的是一个原始的没有被初始化及类型未定义的一块足够大的内存块来存放特定的对象类型.size_t是指需要审请多大的内存块.
这样做很奇怪但这也是我们直接改变调用operator new操作的机会.
void *ptrRawMemory = operator new(sizeof(string));
operator new 返回一个指向一个能装下string对象的内存块。
我们分别来看一下编译器是怎么样处理内存申请与初始化工作的,当编译器看到下面几行代码时,
string *ptrStr = new string("Where is my place in Memory?");
编译器产生的代码像这样,
void *ptrRawMemory = operator new(sizeof(string));
它包括为string对象审请的原始内存.
call string::string("Where is my place in Memory?")
on *ptrRawMemory;
然后编译器调用构造函数初始化这个对象。
string *ptrString = static_cast<string*>(memory);
上面一行使 ptrString 指向新的对象。
当我们使用delete来删除动态审请的对象:
Delete ptr;
这样两件事情发生1,运行的对象有恰当的析构函数,用来调用 operator delete 来释放指定的内存块。
不像其它操作符一样如:operator=,operator new 与 operator delete 两个函数不能重载 new和delete。
Operator new 与 operator delete 的重载版本
注意operator new 与 operator delete 只能用来审请一个单独的对象.为数组审请内存是通过operator new[] 析构用 operator delete[].并也要注意STL容器内存管理不是直接由new与delete管理。
下面是 operator new 与 operator delete 函数的两个重载版本。
void *operator new(size_t); // allocate an object
void *perator new[](size_t); // allocate an array
void *operator delete(void*); // free an object
void *perator delete[](void*); // free an array
New 与 Delete
当我们用new 去动态创建一个对象时,会发生我们上现讨论的两件事情:1,通过调用new 操作符审请内存。2,调用一个或多个构造函数来初始化这块内存。
相似的操作也发生在delete时.一个或多个析构函数被调用,通过 operator delete操作释放内存。
问题是多少个对象驻留在内存中下在被删除?这个问题的答应取决于有多个析构函数被调用。
所以我们要保证new与delete成对出现,下面的示例演示了这个意思.
string *ptrString = new string;
string *ptrStringArray = new string[10];
delete ptrString;
delete [] ptrStringArray;
在这个例子中创建了一个数组,new 操作符的行为与创建一个单独的对象略有不同.内存不是由operator new 创建而是通过operator new[].
看一下创建与删除数组对象的处理过程。
对于数据组来说,必需调用数组中每个对象的构造函数。
string *ptrStringArray = new string[10];
这个代码调用 operator new[] 来审请10个string对象,然后调用每个元素的默认构造函数。
通过这种方式,当我们用delete操作进行删除时,通过 operator delete[] 它调用数组每个元素的析构函数来释放数组内存。
delete [] pstrStringArray;
我们有两种delete的方式:
delete ptrString;
delete [] ptrStringArray;
下面的例子来展示使用delete 与delete[].当我们删除MyClass指针时,我们必需在析构函数中用delete操作符来触发delete myObj来删除堆内存上的数据.
#include <iostream>
class MyClass
{
public:
MyClass()
{
std::cout << "default constructor" << std::endl;
}
MyClass(int s):myArray(new double[s])
{
std::cout << "constructor" << std::endl;
for(int i = 0; i < s; ++i) myArray[i] = 0;
}
~MyClass()
{
// this will be called @"delete myObj"
std::cout << "destructor" << std::endl;
delete[] myArray;
}
private:
double *myArray;
};
int main(int argc, char** argv)
{
MyClass *myObj = new MyClass(5); //'5' here is the number of elements of array of double
delete myObj; // this calls destructor
return 0;
}
如果我们不使用数据版本的delete,我们的程序可能会处于诡异的状态。在一些编译器中,只是第0个元素的析构函数被调用。因为编译器只知道删除了一个对象的指针,另一方便内存错误可能发生,因为new与new[]是完全不同的内存审请方式。
析构函数只在数组中元素是普通对象时,如果是一个数组指针,我们还是需要去分别删除数据组每一个元素的,就象我们分别审请时的样子,请看下面代码的示例:
int main(int argc, char** argv)
{
MyClass**myClassPtrArray = new MyClass*[5];
//Allocate an object for each pointer.
for(int i = 0; i < 5; i++)
myClassPtrArray[i]= new MyClass();
//Use myClassPtrArray.
//Delete each allocated object.
for(int i = 0; i < 4; i++)
deletemyClassPtrArray[i];
//Delete the array itself.
delete[]myClassPtrArray;
}
Free Store (C vs. C++)
C 语言没有提供new和delete操作.使用free来释放内存,我们需要使用函数处理内存。这些函数定义在<stdlib.h>中。
void* malloc(size_t sz) /* allocate sz bytes */
void free(void *p) /* deallocate the memorypointed to by p */
void* calloc(size_t n, size_t sz); /* allocaten*sz bytes initialized to 0 */
void* realloc(void* p, size_t sz); /*reallocate the memory pointed to by p
tp aspace of size sz */