C++堆和栈的区别和联系

本文介绍了C++中内存的5个分区,重点阐述堆和栈的含义、区别,并进行测试比较,发现栈地址向低地址生长且内存连续,堆地址向高地址增长且内存不连续。此外,还讲解了用static控制变量的储存方式和可见性,包括其内部机制、优势等内容。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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;
  • 初始化时使用作用域运算符来标明它所属类;
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值