1、堆和栈的含义
在C++中,内存分为5个区:堆、占、自由存储区、全局/静态存储区、常量存储区
1、栈: 由系统自动分配和释放内存,存放函数的参数值,局部变量的值等,分配方式类似于数据结构中的栈 。
2、堆: 一般由程序员分配和释放内存(由new申请内存,delete释放内存), 若程序员不释放,会造成内存泄露,程序结束时可能由OS回收,分配方式类似于链表 。
3、自由存储区: 是由malloc等分配的内存块,和堆十分相似,用free来释放。
4、全局/静态存储区: 全局变量和静态变量被分配到同一块内存中(在C语言中,全局变量又分为初始化的和未初始化的,C++中没有这一区分)。
5、常量存储区: 里边存放常量,不允许修改。
2、堆和栈的区别
堆(heap) | 栈(stack) | |
---|---|---|
管理方式 | 堆中资源由程序员控制 | 栈资源由编译器自动管理,无需程序员管理 |
内存管理机制 | 系统有一个记录空闲内存地址的链表,当系统收到程序申请时,遍历该链表,寻找第一个空间大于申请空间的堆结点,删除空闲结点链表中的该结点,并将该结点空间分配给程序(大多数系统会在这块内存空间首地址记录本次分配的大小,这样delete才能正确释放本内存 空间,另外系统会将多余的部分重新放入空闲链表中) | 只要栈的剩余空间大于所申请空间,系统为程序提供内存,否则报异常提示栈出 |
空间大小 | 堆是不连续的内存区域(因为系统是用链表来存储空闲内存地址,自然不是连续的),堆大小受限于计算机系统中有效的虚拟内存(32bit 系统理论上是4G),所以堆的空间比较灵活,比较大 | 栈是一块连续的内存区域,大小是操作系统预定好的,windows下栈大小是2M(也有是1M,在编译时确定,VS中可设置) |
碎片问题 | 对于堆,频繁的new/delete会造成大量碎片,使程序效率降低 | 对于栈,它是一个先进后出的队列,进出一一对应,不会产生碎片 |
生长方向 | 堆向上,向高地址方向增长 | 栈向下,向低地址方向增长 |
分配方式 | 动态分配 | 栈有静态分配和动态分配, 静态分配由编译器完成(如局部变量分配),动态分配由alloca函数分配,但栈的动态分配的资源由编译器进行释放,无需程序员实现 |
分配效率 | 堆由C/C++函数库提供,机制很复杂。所以堆的效率比栈低很多 | 栈是极其系统提供的数据结构,计算机在底层对栈提供支持,分配专门 寄存 器存放栈地址,栈操作有专门指令 |
3、测试比较
测试程序如下:
using namespace std;
class Base {
public:
void f() { cout << "Base::f" << endl; }
void g() { cout << "Base::g" << endl; }
void h() { cout << "Base::h" << endl; }
};
int main()
{
Base A;
Base B;
Base C;
Base *D = new Base;
Base E;
Base *F = new Base;
Base *G = new Base;;
cout << "一个class Base的大小为:" << sizeof(Base) << endl;
cout << "一个class Base *D的大小为:" << sizeof(D) << endl;
cout << "地址A:" << (int*)(&A) << endl;
cout << "地址B:" << (int*)(&B) << endl;
cout << "地址C:" << (int*)(&C) << endl;
cout << "地址D:" << D << endl;
cout << "地址E:" << (int*)(&E) << endl;
cout << "地址F:" << F << endl;
cout << "地址G:" << G << endl;
getchar();
}
测试结果如下:
测试结果分析:
1、对于ABCE,其位于栈上,其地址是向低地址方向生长的,内存连续,每次递减1(Base的大小为1),类似于一个先进后出的队列。
2、对于DFG,其位于堆上,其地址是向高地址增长的,而且内存是不连续的。
4、用static来控制变量的储存方式和可见性
static的内部机制
静态数据成员要在程序一开始运行时就必须存在。因为函数在程序运行中被调用,所以静态数据成员不能在任何函数内分配空间和初始化。这样,它的空间分配有三个可能的地方,一是作为类的外部接口的头文件,这里有类声明;二是类定义的内部实现,这里有类的成员函数定义;三是应用程序的 main()函数前的全局数据声明和定义处
static 被引入以告知编译器,将变量存储在程序的静态存储区而非栈上空间,静态数据成员按定义出现的先后顺序依次初始化,注意静态成员嵌套时,要保证所嵌套的成员已经初始化了,消除时的顺序是初始化的反顺序
static的优势
可以节省内存,因为它是所有对象所公有的,因此,对多个对象来说,静态数据成员只存储一处,供所有对象共用。静态数据成员的值对每个对象都是一样,但它的 值是可以更新的。只要对静态数据成员的值更新一次,保证所有对象存取更新后的相同的值,这样可以提高时间效率。引用静态数据成员时,采用如下格式:
<类名>::<静态成员名>
其他:
(1) 类的静态成员函数是属于整个类而非类的对象,所以它没有this指针,这就导致了它仅能访问类的静态数据和静态成员函数。
(2) 不能将静态成员函数定义为虚函数。
(3) 由于静态成员声明于类中,操作于其外,所以对其取地址操作,就多少有些特殊,变量地址是指向其数据类型的指针,函数地址类型是一个“nonmember 函数指针”。
(4) 由于静态成员函数没有 this 指针,所以就差不多等同于 nonmember 函数,结果就产生了一个意想不到的好处:成为一个 callback 函数,使得我们得以将 c++ 和 c-based x window 系统结合,同时也成功的应用于线程函数身上。
(5) static 并没有增加程序的时空开销,相反她还缩短了子类对父类静态成员的访问时间,节省了子类的内存空间。
(6) 静态数据成员在<定义或说明>时前面加关键字 static。
(7) 静态数据成员是静态存储的,所以必须对它进行初始化。
(8) 静态成员初始化与一般数据成员初始化不同:
- 初始化在类的外部进行,而前面不加 static,以免与一般静态变量或对象相混淆;
- 初始化时不加该成员的访问权限控制符private、public;
- 初始化时使用作用域运算符来标明它所属类;