C++内存管理

*爱你&永不变心* 提交于 2021-02-02 05:13:20

原博客:https://www.cnblogs.com/findumars/p/5929831.html?utm_source=itdadao&utm_medium=referral

原博客很长,本博客的目的是摘取目前对自己很有用的信息。

1 内存管理

1.1 C++内存管理详解

1.1.1 内存分配方式

1.1.1.1 简介

在C++中,内存分为:栈、堆、自由存储区、全局/静态存储区、常量存储区。

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

堆,就是那些由malloc等分配的内存块,用free来释放内存。

自由存储区,那些由new分配的内存块,由应用程序去控制,一般一个new就要对应一个delete。如果程序员没有释放掉,那么在程序结束后,操作系统会自动回收。

全局/静态存储区,全局变量和静态变量被分配到同一块内存中,在以前的C语言中,全局变量又分为初始化的和未初始化的,在C++里面没有这个区分了,他们共同占用同一块内存区。

常量存储区,这是一块比较特殊的存储区,他们里面存放的是常量,不允许修改。

1.1.1.2 堆和自由存储区的区别与联系

从技术上来说,堆(heap)是C语言和操作系统的术语。堆是操作系统所维护的一块特殊内存,它提供了动态分配的功能,当运行程序调用malloc()时就会从中分配,稍后调用free可把内存交还。而自由存储是C++中通过new和delete动态分配和释放对象的抽象概念,通过new来申请的内存区域可称为自由存储区。基本上,所有的C++编译器默认使用堆来实现自由存储,也即是缺省的全局运算符new和delete也许会按照malloc和free的方式来被实现,这时藉由new运算符分配的对象,说它在堆上也对,说它在自由存储区上也正确。但程序员也可以通过重载操作符,改用其他内存来实现自由存储,例如全局变量做的对象池,这时自由存储区就区别于堆了。我们所需要记住的就是:

  • 堆是C语言和操作系统的术语、是操作系统维护的一块内存,而自由存储是C++中通过new与delete动态分配和释放对象的抽象概念。堆与自由存储区并不等价。
  • new所申请的内存区域在C++中称为自由存储区。藉由堆实现的自由存储,可以说new所申请的内存区域在堆上。

1.1.1.3 堆和栈的区别

  • 管理方式:对于栈来讲,是由编译器自动管理,无需我们手工控制;对于堆来说,释放工作由程序员控制,容易产生memory leak。
  • 空间大小:一般来讲在32位系统下,堆内存可以达到4G的空间,从这个角度来看堆内存几乎是没有什么限制的。但是对于栈来讲,一般都是有一定的空间大小的,默认的栈空间大小是1M(VS2017 项目-属性-链接器-系统可以修改)。
  • 碎片问题:对于堆来讲,频繁的malloc/free势必会造成内存空间的不连续,从而造成大量的碎片,使程序效率降低。栈是先进后出的队列,以至于永远都不可能有一个内存块从栈中间弹出。
  • 分配方式:堆都是动态分配的,没有静态分配的堆。栈有2种分配方式:静态分配和动态分配。静态分配是编译器完成的,比如局部变量的分配。动态分配由alloca函数进行分配,但是栈的动态分配和堆是不同的,他的动态分配是由编译器进行释放,无需我们手工实现。
  • 分配效率:栈是机器系统提供的数据结构,计算机会在底层对栈提供支持:分配专门的寄存器存放栈的地址,压栈出栈都有专门的指令执行,这就决定了栈的效率比较高。堆则是C/C++函数库提供的,它的机制是很复杂的,例如为了分配一块内存,库函数会按照一定的算法在堆内存中搜索可用的足够大小的空间,如果没有足够大小的空间(可能是由于内存碎片太多),就有可能调用系统功能去增加程序数据段的内存空间,这样就有机会分到足够大小的内存,然后进行返回。显然,堆的效率比栈要低得多。

1.1.2 嵌入式系统--C++动态内存分配

在嵌入式系统中使用C++的一个常见问题是内存分配,即对new 和 delete 操作符的失控。具有讽刺意味的是,问题的根源却是C++对内存的管理非常的容易而且安全。具体地说,当一个对象被消除时,它的析构函数能够安全的释放所分配的内存。这当然是个好事情,但是这种使用的简单性使得程序员们过度使用new 和 delete,而不注意在嵌入式C++环境中的因果关系。并且,在嵌入式系统中,由于内存的限制,频繁的动态分配不定大小的内存会引起很大的问题以及堆破碎的风险。当你必须要使用new 和delete时,你不得不控制C++中的内存分配。你需要用一个全局的new 和delete来代替系统的内存分配符,并且一个类一个类的重载new 和delete

1.1.2.1 为什么需要new/delete

malloc与free是C++/C语言的标准库函数,new/delete是C++的运算符。它们都可用于申请动态内存和释放内存。对于非内部数据类型的对象而言,光用maloc/free无法满足动态对象的要求。对象在创建的同时要自动执行构造函数,对象在消亡之前要自动执行析构函数。由于malloc/free是库函数而不是运算符,不在编译器控制权限之内,不能够把执行构造函数和析构函数的任务强加于malloc/free。因此C++语言需要一个能完成动态内存分配和初始化工作的运算符new,以及一个能完成清理与释放内存工作的运算符delete。最好不要混用。

运算符详解参考:https://blog.csdn.net/zxx910509/article/details/63679774

1.1.2.2 new/delete使用要点

运算符new使用起来要比函数malloc简单得多,例如:

int *p1 = (int *)malloc(sizeof(int) * length);
int *p2 = new int[length];

这是因为new内置了sizeof、类型转换和类型安全检查功能。对于非内部数据类型的对象而言,new在创建动态对象的同时完成了初始化工作。如果对象有多个构造函数,那么new的语句也可以有多种形式。例如:

class Obj
{
 public :
  Obj(void); // 无参数的构造函数
  Obj(int x); // 带一个参数的构造函数
  …
}
void Test(void)
{
 Obj *a = new Obj;
 Obj *b = new Obj(1); // 初值为1
 …
 delete a;
 delete b;
}

如果用new创建对象数组,那么只能使用对象的无参数构造函数。例如:

Obj *objects = new Obj[100]; // 创建100个动态对象

不能写成:

Obj *objects = new Obj[100](1);// 创建100个动态对象的同时赋初值1

在用delete释放对象数组时,留意不要丢了符号‘[]’。例如:

delete []objects; // 正确的用法
delete objects; // 错误的用法

后者有可能引起程序崩溃和内存泄漏。

1.1.2.3 重载全局new和delete操作符

重载参考:https://blog.csdn.net/zxx910509/article/details/64905107

void * operator new(size_t size){
    void *p = malloc(size);
    return (p);
}
void operator delete(void *p);{
    free(p);
} 

也可以对单个类的new 和 delete 操作符重载。

class TestClass {
    public:
        void * operator new(size_t size);
        void operator delete(void *p);
// .. other members here ...
};
void *TestClass::operator new(size_t size)
{
    void *p = malloc(size); // Replace this with alternative allocator
    return (p);
}
void TestClass::operator delete(void *p)
{
    free(p);
}

1.1.2.4 重载new[ ]和delete[ ]

C++将对象数组的内存分配作为一个单独的操作,而不同于单个对象的内存分配。

class TestClass{
    public:
        void *operator new[](size_t size);
        void operator delete[](void *p);
}
void *TestClass::operator new[](size_t size){
    void *p=malloc(size);
    return p;
}
void TestClass:operator delete[](void *p){
    free(p);
}
int main(){
    TestClass *p=new TestClass[10];
    delete[] p;
}

1.1.3 关于内存的一些编程习惯总结

  • 用malloc或new申请内存之后,应该立即检查指针值是否为NULL。防止使用指针值为NULL的内存。
  • 不要忘记为数组和动态内存赋初值。防止将未被初始化的内存作为右值使用。
  • 避免数组或指针的下标越界,特别要当心发生“多1”或者“少1”操作。
  • 动态内存的申请与释放必须配对,防止内存泄漏。
  • 用free或delete释放了内存之后,立即将指针设置为NULL,防止产生“野指针”。

1.1.4 内存耗尽

如果在申请动态内存时找不到足够大的内存块,malloc和new将返回NULL指针,宣告内存申请失败。

1.1.4.1 虚拟内存

虚拟内存的目的是为了让物理内存扩充成更大的逻辑内存,从而让程序获得更多的可用内存。

为了更好的管理内存,操作系统将内存抽象成地址空间。每个程序拥有自己的地址空间,这个地址空间被分割成多个块,每一块称为一页。这些页被映射到物理内存,但不需要映射到连续的物理内存,也不需要所有页都必须在物理内存中。当程序引用到不在物理内存中的页时,由硬件执行必要的映射,将缺失的部分装入物理内存并重新执行失败的指令。

1.2 C++健壮指针和资源管理

先完善C++只是再回头来学

2 内存泄漏

 

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!