Stack
Stack是存在于某作用域(scope)的一块内存空间(memory space)。例如当你调用函数,函数本身即会形成一个stack用来放置它所接收的参数,以及返回地址。
在函数本体(function body)内声明的任何变量,其所使用的内存块都取自上述stack
Heap
Heap或谓system heap,是指由操作系统提供的一块global内存空间,程序可动态分配(dynamic allocated),从中获得若干块(blocks)
例子:
class Complex {...};
...
{ //c1所占用的空间来自stack
Complex c1(1,2);
//Complex(3)是个临时对象,其所占用的空间用new来获得由heap动态分配的内存空间,并由p指向这个空间。动态获得的空间有责任用完后释放其占用的内存空间
Complex* p = new Complex(3)//创建一个复数初值为3,内存来自于heap
}
Note
- 两个对象c1和指针p当离开当前作用域{ }的时候,c1的生命死亡(因为它在栈里面),但是如果你是通过new从堆里面取得的内存或对象等等任何东西,你都必须手动把它释放掉
stack objects的生命期(栈对象)
class Complex {...};
...
{
Complex c1(1,2);//c1只要离开作用域便会死亡,之后析构函数(清理数据)就会被调用
}
c1便是所谓stack object,其生命在作用域(scope)结束之际结束。
这种作用域内的object,又称为auto object,因为它被自动清理
static local objects的生命期(静态局部对象)
class Complex {...};
...
{
static Complex c2(1,2);
}
c2便是所谓static object,其生命在作用域(scope)结束之际仍然存在直到整个程序结束。
global objects的生命期(全局对象)
class Complex {...};
...
Complex c3(1,2);
int main()
{
...
}
c3便是所谓global object,其生命在整个程序结束之际才结束。你也可以把它视为一种static object,其作用域是整个程序
heap objects的生命期
class Complex { ... };
...
{
Complex* p = new Complex;
...
delete p;
}
p所指的便是heap object,其生命在它被delete之际结束
以下情况出现内存泄漏
class Complex {...};
...
{
Complex* p = new Complex;
}
以上出现内存泄漏(Memory leak),因为当作用域结束,p所指的heap object仍然存在,但指针p的生命却结束了,作用域之外再也看不到p(也就没机会delete p)
new:先分配memory,再调用构造函数ctor
以复数为例:

Complex* pc = new Complex(1,2);
编译器将new Complex()转化为
- 第一步:分配内存
- 第二步:转型
- 第三步:调用构造函数
Conplex *pc;
//第一步:分配内存
void* mem = operator new( sizeof(Complex) );//sizeof(Complex)大小为8,复数的数据类型为double(占8个字节),//operator new()其内部调用malloc(n)
//第二步:转型
pc = static_cast<Complex*>(mem);
//构造函数的全名Complex::Complex(pc,1,2); 下面的构造函数的参数隐藏了pc
pc -> Complex::Complex(1,2);//构造函数Complex()和类Complex
//pc为上图两个double所在内存的起始位置
以字符串为例:

String* ps = new String("Hello");
编译器将new String()转化为
- 第一步:分配内存
- 第二步:转型
- 第三步:调用构造函数
String* ps;
{
//第一步:分配内存
void* mem = operator new( sizeof(String) );//其内部调用malloc(n)
//第二步:转型
pc = static_cast<String*>(mem);
//第三步:调用构造函数
//构造函数的全名String::String(pc,"Hello"); 下面的构造函数的参数隐藏了ps
pc -> String::String("Hello");//构造函数String()和类Complex
}
delete:先调用析构函数dtor,再释放memory
- delete和new过程相反
以复数为例:

Complex* pc = new Complex(1,2);
...
delete pc;
编译器将delete pc;转化为
- 第一步:调用析构函数
- 第二步:释放内存
//第一步:调用析构函数
Complex::~Complex(pc);
//第二步:释放内存
operator delete(pc);
以字符串为例:

String* ps = new String("Hello");
...
delete ps;
编译器将delete ps;转化为
- 第一步:调用析构函数(清理数据)
- 第二步:释放内存(清除占用空间)
//第一步:调用析构函数
String::~String(ps);//析构函数要把动态分配的空间删掉,至于字符串本身只是一个指针
//第二步:释放内存
operator delete(ps);
动态分配所得的内存块(Memory block)in VC(在VC中给的每一个内存块一定是16的倍数)

第一部分:复数
-
大图中第一列(在Debug mode下)

1.创建一个复数的实部虚部(2个double)得到 8 byte (绿色区域)2.调试模式下多得到 32 byte(complex上方)+4 byte(complex下方) (灰色区域)
3.2个 cookie(一个cookie有 4 byte) (橘色区域)
[复数8 byte+调试模式下(32+4)byte+上下两个cookie(4×2)=52,52不能整除16,填充为64]
[free()和malloc()函数说好了,在内存块上下放2个cookie,这里64的16进制为40,红色区域记录值为41,40借用最后一位(bit),用0或1表示这一块内存是给出去还是收回来,这里对操作系统而言是给出去内存了,对程序而言是获得了内存,获得了内存区域,所以这里是41,这里最后一位是1,如果将这块内存区域还给操作系统则变为40,最后一位变为0]
[为什么可以借1个bit使用呢?因为内存填充调整到16的倍数,16倍数的话最后4个bit都是0,所以可以借其中一位(bit)来用]4.填充 pad(不能整除16时,将内存填充为16的倍数) (蓝绿色区域)
-
大图中第二列(在Release mode下)

1.创建一个复数对象(实部虚部,2个 double)得到 8 byte (绿色区域)2.2个 cookie(记录整块内存大小)(一个cookie有 4byte) (橘色区域)
[复数8 byte+上下两个cookie(4*2)=16,不需要填充pad]
[16的十六进制表示是1,程序获得了内存区域,所以这里是11(最后一位为1)]
第二部分:字符串
-
大图中第三列(在Debug mode下)

1.创建一个字符串对象(字符串本身只内含一个指针,所以它的大小是指针的大小 4byte) (绿色区域)2.调试模式下多得到 32byte(complex上方)+4 byte(complex下方) (灰色区域)
[String object 4 byte+(32+4)+(4*2)(2个cookie,1 cookie 个4 byte)=48,48是16的倍数,48对应的十六进制为3,所以不需要pad填充] -
大图中第四列(在Release mode下)

1.创建一个字符串对象(字符串本身只内含一个指针,所以它的大小是指针的大小 4 byte) (绿色区域)2.2个cookie(记录整块内存大小)(一个cookie有 4 byte) (橘色区域)
[4 byte + (4*2)byte = 12 byte,12不是16的倍数,所以需要pad填充]
[填充4 byte(一小格)成为16 byte,16的十六进制是1(11的第一个1),程序获得内存区域最后一位为1(11的最后一个1),所以这里是11。如果把内存还给操作系统,11就会变成10]3.填充pad(不能整除16时,将内存填充为离当前内存值最近的 16的倍数) (蓝绿色区域)
动态分配所得的数组(Array)

第一部分:复数
- 大图中第一列(在Debug mode下)
Complex* p = new Complex[3];

1.一个复数 (实部虚部)2个double(double 8 byte),这里数组大小为 3,内存大小8×3 (灰色区域)
2.调试模式下分配 32 byte(数组大小3的上方)+4 byte(数组大小3的下方) (橘色区域)
3.上下共2个cookie(4×2)(1个cookie 4byte) (白色区域51h,h为十六进制,80用十六进制表示为5,程序获得内存区域最后一位用1表示)
4.在VC中(其他的不一定是)图中有1格(4 byte)放数组大小,这里图中为数字3表示有3对double
5.(全部加起来为72 byte)pad填充到 80 (蓝绿色区域)
- 大图中第二列(在Release mode下)
Complex* p = new Complex[3];

1.一个复数(实部虚部) 2个 double(double 8 byte),这里数组大小为3,内存大小 8×3 (灰色区域)
2.上下共 2个 cookie(4×2)(1个cookie 4byte) (白色区域31h,h为十六进制,3是48的十六进制表示,最后一位用1表示程序获得内存区域)
3.在VC中(其他的不一定是)图中有1格(4 byte)放数组大小,这里图中为数字 3表示有 3个复数(3对 double)
4.(全部加起来为 36 byte)pad填充到 48 (蓝绿色区域)
第二部分:字符串
- 大图中第三列(在Debug mode下)
String* p = new String[3];

1.一个指针占一小格(1格4 byte),这里数组大小为 3,内存大小 4×3(3个指针) (灰色区域)
2.调试模式下分配 32 byte(数组大小3的上方)+4 byte(数组大小3的下方) (橘色区域)
3.上下共 2个 cookie(4×2)(1个cookie 4byte) (白色区域41h,h为十六进制,64的十六进制表示为4,程序获得内存区域,最后一位用1表示)
4.在VC中(其他的不一定是)图中有 1格(一格内有一个指针,4 byte)放数组大小,这里图中为数字 3表示3个字符
5.(全部加起来为60 byte)pad填充到 64 (蓝绿色区域)
- 大图中第四列(在Release mode下)
String* p = new String[3];

1.一个指针占一小格(1格4 byte),这里数组大小为 3,内存大小 4×3(3个指针) (灰色区域)
2.上下共 2个 cookie(4×2)(1个cookie 4byte) (白色区域21h,h为十六进制,32的十六进制表示为2,程序获得内存区域,最后一位用1表示)
3.在VC中(其他的不一定是)图中有 1格(一格内有一个指针,4 byte)放数组大小,这里图中为数字 3表示3个字符
在字符串中array new 一定要搭配 array delete
- array new (本例中 m_data = new char[strlen(cstr)+1] ; )
- array delete(本例中delete[] m_data;//[ ] 表明要删除的是数组,而不是一个字符,唤起3次析构函数,把动态分配的内存删掉)

内存泄漏的地方

在字符串中array new 一定要搭配 array delete,像在复数中=因为它没有指针也不会做动态分配,即使你没有用array new和array delete也可以,因为复数它没有下图这种东西

但要养成好习惯你要用array new就一定要搭配array delete保证万无一失
本文详细介绍了C++中堆栈和内存管理的相关知识。阐述了栈、堆的概念,不同对象(栈对象、静态局部对象、全局对象、堆对象)的生命周期,以及new和delete的操作过程。还分析了VC中动态分配内存块和数组的情况,强调了避免内存泄漏,如字符串中array new要搭配array delete。
1万+

被折叠的 条评论
为什么被折叠?



