rambo

深拷贝与浅拷贝

自由存储区内存的分配与释放

当程序运行时遇到一个需要动态分配的变量或对象时,必须向系统申请取得自由存储区中一块所需大小的存储空间,用于存储该变量或对象。当它的生命结束时,要显示释放它所占用的存储空间。

自由存储时由new/delete分配释放的,堆由malloc/free分配释放。

new/delete,和malloc/free在功能上的区别:

1)C++中的对象有构造和析构函数,因此new/delete除了申请释放内存之外还会调用构造析构函数,在实现上,有些编译器在执行new/delete的内存功能时调用malloc/free。 new()和delete()是可以重载的,他们都是类的静态成员函数。

gouzao

2)malloc/free是标准C函数,只会申请所需的内存,而不会去调用构造/析构函数。

其实电脑开机后物理内存的每个字节都是可读写的,区别仅在于操作系统内存管理模块在你读写时是否能发现并是否采取相应动作而已。操作系统管理内存的粒度不是字节而是页,一页通常为4KB。

注意:这时释放了pi所指的目标的内存空间,即动态内存释放,但是指针pi本身并没有撤销,该指针本身所占内存空间并未释放。 pi变成悬空指针(指针未指向一个有效地址)。建议将pi置空(NULL)。

在程序会先确定在堆中分配内存的大小,然后调用 operator new 分配内存,然后返回这块内存的首地址,放入栈中。

补充知识:

  • 4个数据寄存器(EAX、EBX、ECX和EDX)
  • 2个变址和指针寄存器(ESI和EDI)
  • 2个指针寄存器(ESP和EBP)

eax:保存所有API函数的返回值。寄存器AX和AL通常称为累加(Accumulator),用累加器进行的操作可能需要更少时间。累加器可用于乘、除、输入/输出等操作,它们的使用频率很高;
ebx:基地址寄存器(Base Register)。它可作为存储器指针来使用;
ecx:称为计数寄存器(Count Register)。在循环和字符串操作时,要用它来控制循环次数;在位操作中,当移多位时,要用CL来指明移位的位数;
edx:称为数据寄存器(Data Register)。在进行乘、除运算时,它可作为默认的操作数参与运算,也可用于存放I/O的端口地址。

esp:栈指针寄存器(extended stack pointer),其内存放着一个指针,该指针永远指向系统栈最上面一个栈帧的栈顶。
ebp:基址指针寄存器(extended base pointer),其内存放着一个指针,该指针永远指向系统栈最上面一个栈帧的底部。

标准字符串类string就是采用动态建立数组的方式解决数组溢出的问题。

 深复制与浅复制

  1. 浅复制存在的问题

C++类/结构中有一些隐含的成员函数,如默认构造函数,拷贝构造函数、析构函数以及拷贝赋值操作符。拷贝构造函数和拷贝赋值操作符会执行按位。

比如,类中有一个数据成员为指针,则该类的一个对象obj1中的这个指针p指向了动态分配的一个自由存储区对象。如果用obj1按成员复制了一个对象obj2,则obj2.p也指向同一个自由存储区对象。(只会对指针的地址进行拷贝,而不会拷贝对象所指的对象,如图示)。问题来了,1)修改obj1的自由存储区对象,obj2也会改变。2)当析构时,如果使用默认析构函数,则动态分配的自由存储区对象不能回收,必须自定义析构函数(包含释放自由存储区对象的delete语句)。如果先析构obj1,则自由存储去已经释放,结果obj2就缺了一个自由存储区对象。

qianfuzhi      2.深复制

解决方法:深拷贝,显示编写拷贝构造函数和拷贝赋值操作符。

重新定义赋值构造函数,给每个对象分配一个独立的自由存储区对象。如图 ,先复制对象主体,再为obj2分配一个自由存储区对象,最后用obj1的自由存储区对象复制obj2的自由存储区对象。但如果用于初始化或者复制的对象来自于右值(临时对象),这时仍不得不拷贝它的值,但很快该临时对象就会被析构以释放资源。这样浪费了很多系统开销,包括内存分配以及拷贝数据。

      3.右值引用

C++11引入右值引用(&&)。移动语义允许修改右值。移动构造函数和移动赋值操作符,接受一个类型T&&的参数,这两个函数可以修改对象,相当于把指针指向的对象"偷"来。举例,某个容器的具体实现(比如vector或者queue)可能会包含一个指向元素数组的指针。如果需要创建vector临时量或者从函数中返回vector,(我们不需要申请另一个数组,然后拷贝临时对象的值,并在临时对象被销毁时删除这段内存),而是只拷贝所分配的数组的指针地址,这样节省了分配、拷贝一系列元素然后析构的开销。