变量内存释放和析构函数顺序

本文探讨了Linux x86_64系统下C++程序中不同类型的变量(全局、静态、局部)在内存中的分布以及释放顺序。栈内存存放局部变量,堆内存由用户管理,数据段包括bss、data和rodata段,代码段包含text和init段。全局变量和静态变量在数据段,局部变量在栈,堆内存由malloc分配并在free时释放。变量释放顺序遵循声明定义的逆序,局部静态变量先于全局变量释放。总结了内存释放的三条原则,并通过实例验证了释放顺序:局部变量>静态局部变量>全局变量。

最近发现一个很有趣的事情,就是全局变量、全局静态变量、局部静态变量、局部变量中变量的释放顺序。下述均属Linux x86_64系统下C++的运行情况。

一、内存分布

众所周知,一个进程的内存空间主要分为:栈内存、堆内存、数据段、和代码段,另外内核区和不可访问区不在此讲述。
在这里插入图片描述

1.栈内存

栈的全称是“运行时栈(Run-time Stack)”,顾名思义就是随着程序运行、变量的进栈和出栈而不断改变。局部变量存放在该位置。

2堆内存.

堆内存是一块自由内存,由用户来申请和释放。堆内存全称是“运行时堆(Run-time Heap)”,和栈一样内存大小随着程序的运行而变化。

3.数据段

数据段包含三个部分,地址由低到高分别是:bss段、data段、rodata段。其中bss是存放未初始化的静态数据(系统自动初始化为0,节省磁盘空间),data是存放已初始化的静态数据,rodata是存放只读数据,比如字符串、字符常量等。所谓静态数据,包括全局变量和static关键字修饰的变量。

4.代码段

代码段分为两个部分:text段和init段。text段用来存放用户程序代码,而init段则用来存储系统给每一个可执行程序自动添加的“初始化”代码,这部分代码功能包括环境变量的准备、命令行参数的组织和传递等,并且这部分数据被放在了栈底。

总结:

int a;							//全局变量,存放在bss段
static int b=1;					//全局静态变量,存放在data段
int main()
{
	int c;						//局部变量,存放在栈
	int * p = (int *)malloc(4);	//存放在堆
	static int d;				//局部静态变量,存放在数据段的bss
	free(p);
	return 0;
}

二、变量释放顺序

声明定义了四个类,分别是A、B、C、D,每个类重写析构函数,用来判断内存释放顺序,代码如下:

#include <iostream>
using namespace std;

class A{
public:
    ~A(){
        cout << "A释放内存" << endl;
    }
};

class B{
public:
    ~B(){
        cout << "B释放内存" << endl;
    }
};

class C{
public:
    ~C(){
        cout << "C释放内存" << endl;
    }
};

class D{
public:
    ~D(){
        cout << "D释放内存" << endl;
    }
};
//主函数具体代码如下:
1、相同变量类型,先声明的后释放。

在这里插入图片描述

2、不同变量类型,释放顺序与位置无关,与变量类型有关。

在这里插入图片描述
static变量的生命周期为整个程序,如图static和局部变量在同一作用域内,局部变量比static变量先释放内存。

在这里插入图片描述
上图说明不管是普通全局变量还是静态全局变量,其顺序只看声明定义顺序。而局部静态变量和全局静态变量相比,局部静态变量先释放内存。

3、由用户申请的堆内存,在用户手动释放的时候释放,若没有释放,会造成内存泄漏。
总结:

1)同类型变量(此处类型为:全局变量、局部变量、静态变量)下,先声明定义的后释放内存。
2)不同类型变量顺序为:普通局部变量>静态局部变量>全局变量。
3)静态全局变量和普通全局变量归为第一点。

三、举例子

如图:在同一作用域下,验证到优先级为:普通全局变量>静态全局变量>普通全局变量,由用户申请的内存使用delete的时候释放。
在这里插入图片描述

C++ 中,当创建对象时,首先会调用该类的构造函数来初始化对象成员变量数据成员。而当对象被销毁时,会自动调用析构函数来清理对象并释放相关资源。 构造函数析构函数的调用顺序与对象的创建销毁顺序密切相关。对于单个对象,构造函数的调用先于析构函数。而对于多个对象,它们的构造函数析构函数的调用顺序则取决于它们的创建销毁顺序。 具体来说,创建对象时,构造函数的调用顺序按照成员变量的声明顺序进行,即先调用基类构造函数,再调用成员变量的构造函数,最后调用自身的构造函数。而销毁对象时,析构函数的调用顺序则与构造函数相反,即先调用自身的析构函数,再调用成员变量析构函数,最后调用基类的析构函数。 需要注意的是,如果一个类是另一个类的成员变量,则其构造函数析构函数的调用顺序取决于它们在类中的声明顺序。如果一个类是另一个类的基类,则其构造函数析构函数的调用顺序与继承方式有关。如果是虚继承,则先调用最远的祖先类的构造函数,再依次调用中间类自己的构造函数;而析构函数的调用顺序则相反,先调用自己的析构函数,再依次调用中间类最远的祖先类的析构函数。 总之,构造函数析构函数的调用顺序C++ 中一个非常重要的概念,需要开发者在编写程序时仔细考虑。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值