C++内存管理

深拷⻉和浅拷⻉的区别

主要区别在于如何处理对象内部的动态分配的资源。

深拷⻉

深拷⻉是对对象的完全独⽴复制,包括对象内部动态分配的资源。在深拷⻉中,不仅复制对象的值,还会复制对象
所指向的堆上的数据。

主要特点:

  • 复制对象及其所有成员变量的值。
  • 动态分配的资源也会被复制,新对象拥有⾃⼰的⼀份资源副本。

深拷⻉通常涉及到⼿动分配内存,并在拷⻉构造函数或赋值操作符中进⾏资源的复制。

浅拷贝

浅拷⻉仅复制对象的值,⽽不涉及对象内部动态分配的资源。在浅拷⻉中,新对象和原对象共享相同的资源,⽽不
是复制⼀份新的资源。

主要特点:

  • 复制对象及其所有成员变量的值。
  • 对象内部动态分配的资源不会被复制,新对象和原对象共享同⼀份资源。

浅拷⻉通常使⽤默认的拷⻉构造函数和赋值操作符,因为它们会逐成员地复制原对象的值。

内存分区

C/C++编译的程序占用的内存分区

在C++中,内存分成5个区,他们分别是堆、栈、自由存储区、全局/静态存储区和常量存储区。
在这里插入图片描述

栈,在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。

堆,堆⽤于存储动态分配的内存的区域,由程序员⼿动分配和释放。使用 new 和 delete 或 malloc 和 free 来进⾏堆内存的分配和释放。
  
  全局/静态存储区,全局区存储全局变量和静态变量。⽣命周期是整个程序运⾏期间。在程序启动时分配,程序结束时释放。在以前的C语言中,全局变量又分为初始化的和未初始化的,在C++里面没有这个区分了,他们共同占用同一块内存区。

常量存储区,常量区也被称为只读区,这是一块比较特殊的存储区,他们里面存放的是常量,不允许修改。
  
  代码区,存储程序的代码。

堆和栈的区别

栈和堆都是⽤于存储程序数据的内存区域。栈是⼀种有限的内存区域,⽤于存储局部变量、函数调⽤信息等。堆是⼀种动态分配的内存区域,⽤于存储程序运⾏时动态分配的数据。

栈上的变量⽣命周期与其所在函数的执⾏周期相同,⽽堆上的变量⽣命周期由程序员显式控制,可以(使⽤ new 或 malloc)和释放(使⽤ delete 或 free )。

栈上的内存分配和释放是⾃动的,速度较快。⽽堆上的内存分配和释放需要⼿动操作,速度相对较慢。

堆和自由存储区的区别

堆是操作系统维护的一块内存,而自由存储是C++中通过new与delete动态分配和释放对象的抽象概念。堆与自由存储区并不等价。

malloc函数从堆上动态分配内存,也就是说函数返回的指针是指向堆里面的一块内存。操作系统中有一个记录空闲内存地址的链表。当操作系统收到程序的申请时,就会遍历该链表,然后就寻找第一个空间大于所申请空间的堆结点,然后就将该结点从空闲结点链表中删除,并将该结点的空间分配给程序。

new操作符从自由存储区(free store)上为对象动态分配内存空间,自由存储区是C++基于new操作符的一个抽象概念,凡是通过new操作符进行内存申请,该内存即为自由存储区。而堆是操作系统中的术语,是操作系统所维护的一块特殊内存,用于程序的内存动态分配,C语言使用malloc从堆上分配内存,使用free释放已分配的对应内存。自由存储区不等于堆,堆是一个实际的区域,而自由存储区是一个更上层的概念。通常new确实是在堆上申请内存,但是程序员可以自己重载new操作符,使用其他内存来实现自由存储(这并不常见)。另外,c++ primer plus这本书上有提到布局new,可以为对象在栈上分配内存。总的来说,自由存储区是new申请的区间的概念。如上所述,布局new就可以不位于堆中。

内存对齐

什么是内存对齐

内存对⻬是指数据在内存中的存储起始地址是某个值的倍数。

在C语⾔中,结构体是⼀种复合数据类型,其构成元素既可以是基本数据类型(如int、long、float等)的变量,也可以是⼀些复合数据类型(如数组、结构体、联合体等)的数据单元。在结构体中,编译器为结构体的每个成员按其⾃然边界(alignment)分配空间。各个成员按照它们被声明的顺序在内存中顺序存储,第⼀个成员的地址和整个结构体的地址相同。

为了使CPU能够对变量进⾏快速的访问,变量的起始地址应该具有某些特性,即所谓的“对⻬”,⽐如4字节的int型,其起始地址应该位于4字节的边界上,即起始地址能够被4整除,也即“对⻬”跟数据在内存中的位置有关。如果⼀个变量的内存地址正好位于它⻓度的整数倍,他就被称做⾃然对⻬。

⽐如在32位cpu下,假设⼀个整型变量的地址为0x00000004(为4的倍数),那它就是⾃然对⻬的,⽽如果其地址为0x00000002(⾮4的倍数)则是⾮对⻬的。现代计算机中内存空间都是按照byte划分的,从理论上讲似乎对任何类型的变量的访问可以从任何地址开始,但实际情况是在访问特定类型变量的时候经常在特定的内存地址访问,这就需要各种类型数据按照⼀定的规则在空间上排列,⽽不是顺序的⼀个接⼀个的排放,这就是对⻬。

为什么需要内存对齐

**需要字节对⻬的根本原因在于CPU访问数据的效率问题。**假设上⾯整型变量的地址不是⾃然对⻬,⽐如为0x00000002,则CPU如果取它的值的话需要访问两次内存,第⼀次取从0x00000002-0x00000003的⼀个short,第⼆次取从0x00000004-0x00000005的⼀个short然后组合得到所要的数据,如果变量在0x00000003地址上的话则要访问三次内存,第⼀次为char,第⼆次为short,第三次为char,然后组合得到整型数据。

⽽如果变量在⾃然对⻬位置上,则只要⼀次就可以取出数据。⼀些系统对对⻬要求⾮常严格,⽐如sparc系统,如果取未对⻬的数据会发⽣错误,⽽在x86上就不会出现错误,只是效率下降。

各个硬件平台对存储空间的处理上有很⼤的不同。⼀些平台对某些特定类型的数据只能从某些特定地址开始存取。⽐如有些平台每次读都是从偶地址开始,如果⼀个int型(假设为32位系统)如果存放在偶地址开始的地⽅,那么⼀个读周期就可以读出这32bit,⽽如果存放在奇地址
开始的地⽅,就需要2个读周期,并对两次读出的结果的⾼低字节进⾏拼凑才能得到该32bit数据。显然在读取效率上下降很多。

⼤多数计算机硬件要求基本数据类型的变量在内存中的地址是它们⼤⼩的倍数。例如,⼀个 32 位整数通常需要在内存中对⻬到 4 字节边界。
内存对⻬可以提⾼访问内存的速度。当数据按照硬件要求的对⻬⽅式存储时,CPU可以更⾼效地访问内存,减少因为不对⻬⽽引起的性能损失。
许多计算机体系结构使⽤缓存⾏(cache line)来从内存中加载数据到缓存中。如果数据是对⻬的,那么⼀个缓存⾏可以装载更多的数据,提⾼缓存的命中率。
有些计算机架构要求原⼦性操作(⽐如原⼦性读写)必须在特定的内存地址上执⾏。如果数据不对⻬,可能导致⽆法执⾏原⼦性操作,进⽽引发竞态条件。

变量的内存占用

基本变量在不同位的操作系统下占用字节数

在这里插入图片描述

sizeof(string)

string类的大小是固定的,不会随着字符串变化而变化。

在不同的编译器上sizeof(string)的大小并不相同
在gcc编译器上为8字节
在vs2022上为40字节,在vs2008上是32字节

结构体和类

空结构体的变量和空类的对象占1字节

内存对齐的三条规则(类也适用)

数据成员对齐规则,结构体(struct)(或联合(union))的数据成员,第一个数据成员存放在offset为0的地方,以后每个数据成员存储的起始位置要从该成员大小或者成员的子成员(只要该成员有子成员,比如数组、结构体等)大小的整数倍开始(如:int 在 64bit 目标平台下占用 4Byte,则要从4的整数倍地址开始存储)

结构体作为成员,如果一个结构体里有某些结构体成员,则结构体成员要从其内部最大元素大小的整数倍地址开始存储

结构体的总大小,即sizeof的结果,必须是其内部最大成员长度(即前面内存对齐指令中提到的有效值)的整数倍,不足的要补齐

类内成员的内存占用

类内部的成员变量:
普通的变量:是要占用内存的,但是要注意内存对齐(这点和struct类型很相似)。
static修饰的静态变量:不占用内存,原因是编译器将其放在全局变量区。
从父类继承的变量:计算进子类中

类内部的成员函数:
非虚函数(构造函数、静态函数、成员函数等):不占用内存。
虚函数:要占用4个字节(32位的操作系统),用来指定虚拟函数表的入口地址。跟虚函数的个数没有关系。父类子类工享一个虚函数指针。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值