栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > C/C++/C#

C++ 堆/栈区别,new的用法,以及与malloc的区别

C/C++/C# 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

C++ 堆/栈区别,new的用法,以及与malloc的区别

1、堆与栈的区别

首先从虚拟内存分布上可以看出,栈是由高地址向低地址扩展,堆是由低地址向高地址扩展。

在管理上:

堆内存由程序员手动管理,用new/delete 或者 malloc/free 手动进行分配与回收。如果不进行回收的话,会造成内存泄漏的问题。

栈内存是由编译器管理,不需要程序员管理,会自动分配回收空间,一般保存的是局部变量和函数参数。相比于堆,栈的空间小很多,但空间是连续的,不会有内存碎片的存在。

1、函数调用时,栈帧发生的变化

 在其中,有一个问题需要注意,在函数调用的时候,对参数的压栈是从参数列表的右边开始压栈。至于为什么从右边进行压栈,而不是从左边进行,有一个说法是,在C++/C 中,有可变参数的函数,如果是从左向右压栈,第⼀个参数(即描述可变参数表各变类型的那个参数)将被放在 栈底,由于可变参的函数第⼀步就需要解析可变参数表的各参数类型,即第⼀步就需要得到上述参数,因此,将它放在栈底是很不⽅便的。)在函数调用的过程中,也不会产生内存碎片。

2、操作系统管理申请内存

实际上系统中有⼀个空闲链表,当有程序申请的时候,系统遍历空闲链表找到第⼀个⼤于等于申请 ⼤⼩的空间分配给程序,⼀般在分配程序的时候,也会空间头部写⼊内存⼤⼩,⽅便 delete 回收空间⼤⼩。当然 如果有剩余的,也会将剩余的插⼊到空闲链表中,这也是产⽣内存碎⽚的原因。

参考:https://zhuanlan.zhihu.com/p/464435500

对于内存池的应用:

在C/C++中我们通常使用malloc,free或new,delete来动态分配内存。

一方面,因为这些函数涉及到了系统调用,所以频繁的调用必然会导致程序性能的损耗;

另一方面,频繁的分配和释放小块内存会导致大量的内存碎片的产生,当碎片积累到一定的量之后,将无法分配到连续的内存空间,系统不得不进行碎片整理来满足分配到连续的空间,这样不仅会导致系统性能损耗,而且会导致程序对内存的利用率低下。

当然,如果我们的程序不需要频繁的分配和释放小块内存,那就没有使用内存池的必要,直接使用malloc,free或new,delete函数即可。

2、认识new

        在侯捷老师的课程中,new被分为 new expression;operator new 和 placement new 三种。其中new expression既要分配内存,还有调用构造函数;operator new仅分配内存,不调用构造函数;placement new 不分配内存,仅调用构造函数。

在cpp reference中,operator new有三种重载:下面介绍其中的两个,抛异常的new 与 指定地址的new

throwing (1)	
void* operator new (std::size_t size);
nothrow (2)	
void* operator new (std::size_t size, const std::nothrow_t& nothrow_value) noexcept;
placement (3)	
void* operator new (std::size_t size, void* ptr) noexcept;
1、new 的类型和使用

定义Myclass类。

#include 
struct MyClass {
  MyClass() {std::cout <<"MyClass constructedn";}
  ~MyClass() {std::cout <<"MyClass destroyedn";}
};

new expression:new/delete

int main () {
  MyClass * pt = new MyClass();
  delete pt;    // implicitly calls ::operator delete(pt)
} 

output: MyClass constructed
         MyClass destroyed

new和delete会先分配内存,然后调用构造函数。因此输出调用的语句如上。

::operator new()/::operator delete()

int main () {
  MyClass * pt = (MyClass*) ::operator new(sizeof(MyClass));
  ::operator delete(pt);
  return 0;
}

上述代码不会输出任何结果,因为operator new并不会调用构造函数,上面的代码只是创建了一个MyClass类型的指针,然后就释放了,没有其余操作。

placement new

int main() {
    MyClass* p1 = (MyClass*) ::operator new (sizeof(MyClass));
    new(p1) MyClass;//placement new
    p1->~MyClass();//指针可以直接调用析构函数
    ::operator delete(p1);
    p1 = NULL;//防止产生野指针
    return 0;
}
[output]    MyClass constructed
            MyClass destroyed

2、cpp reference的例子

#include   
struct MyClass {
    int *data = 0;
    MyClass() {std::cout << "constructed [" << this << "]n";}
    ~MyClass() {delete data;std::cout << "destroyed [" << this << "]n";}
};
int main () {
    std::cout << "1: ";
    MyClass * p1 = new MyClass;
    // allocates memory by calling: operator new (sizeof(MyClass))
    // and then constructs an object at the newly allocated space

    std::cout << "2: ";
    MyClass * p2 = new (std::nothrow) MyClass;
    // allocates memory by calling: operator new (sizeof(MyClass),std::nothrow)
    // and then constructs an object at the newly allocated space

    std::cout << "3: ";
    new (p2) MyClass;
    // does not allocate memory -- calls: operator new (sizeof(MyClass),p2)
    // but constructs an object at p2

    // Notice though that calling this function directly does not construct an object:
    std::cout << "4: ";
    MyClass * p3 = (MyClass*) ::operator new (sizeof(MyClass));
    // allocates memory by calling: operator new (sizeof(MyClass))
    // but does not call MyClass's constructor
    std::cout< 

以上代码是c++ reference摘过来的,原文中使用的是delete p3,此时就会调用p3的析构函数,然而p3并未构造任何对象,引发错误如下,摧毁了一个未知区域未定义的对象。改为::operator delete(p3),应为正确。

3、placement new如何用?

        placement new是对operator new的重载,对于效率和容错要求比较高的程序来说placement new是很有用的:

1)placement new能够有效的解决内存碎片的问题;

2)malloc、new等申请内存有失败的可能,对有些程序来说是不允许的,而placement new可以做到;

3)placement new不需要执行申请内存的过程,速度更快;​​​​​​​

普遍的使用方法如下:
class A{
       …
public:
       doSomething();
};

1)分配内存
char* buff = new char[ sizeof(A) *N+sizeof(int)) ];
memset( buff, 0, sizeof(A)*N +sizeof(int));

2)构建对象
A* pa = new (buff)A;

3)使用对象
pa->doSomething();

4)析构对象,显式的调用类的析构函数。
pa->~A();

5)销毁内存
delete [] buff;

对于buff这块内存可以反复使用,只要重复2)、3)、4)步骤即可。在C++标准中,对于placement operator new []有如下的说明:placement operator new[] needs implementation-defined amount of additional storage to save a size of array. 所以我们必须申请比原始对象大小多出sizeof(int)个字节来存放对象的个数,或者说数组的大小。

4、为什么没有placement delete

因为很简单的道理,先看placement new的作用,定位放置:“new (pointer) ClassName”,旨在使用pointer调用构造函数,并不分配内存;如果有对应的placement delete的话就需要只调用析构函数,而不释放内存,而指针是可以直接显式的调用析构函数的(尽管并不推荐)。此时placement delete的设计就会很多余。

5、总结

平时使用:new+delete​​​​​​​

MyClass * p1 = new MyClass;
delete p1;

自己写库:operator new+placement new+析构+operator delete

MyClass * p2 = (MyClass*) ::operator new (sizeof(MyClass));
new (p2) MyClass;//placement new
p2->~MyClass();
::operator delete(p2);

记得operator new和operator delete要成对出现!!​​​​​​​

第三四行可以被替代为:

 delete p2;// 或者delete(p2),两者等同

参考:https://mp.weixin.qq.com/s/M2f2k70mZbkaxcl0ZoawcQ

3、new/delete 与 malloc/free 的区别

        它们都用于申请动态内存和释放内存。malloc/free 是 c/c++ 中的标准库函数,new/delete是c++中的运算符。

  • new 与 malloc 的区别:

执行内容

        malloc,是按字节来分配内存的(意思是分配的大小,由自己指定)

        new,是先开辟内存(由编译器根据你的对象类型,来决定开辟内存的大小),然后对内存进行初始化(执行构造函数),返回正确指向数据的指针。

返回类型

        malloc,返回类型是void* ,因此在使用的时候,需要强制转换类型;如果申请内存失败,返回nullptr指针;

        new,返回的是申请对象的指针,如果申请内存失败,抛出 bad_alloc类型的异常;因此两者判断是否开辟成功的所做也不一样。

  • delete 与 free 的区别

执行过程

        delete:先调用析构函数,然后释放内存; free 只释放内存。

        根据delete的工作步骤,如果是针对于内置数据类型,例如 int,float等,new并不需要执行构造函数;delete并不需要执行析构函数,因此和malloc,free此时并没有什么区别。

参考:

https://blog.csdn.net/JACKSONMHLK/article/details/123298801?spm=1001.2014.3001.5501

4、有了malloc/free 为何还需要new/delete?

        对于非内部数据(与内部数据并列理解,内部数据就是编译器本身就认识,比如int、float、char等,非内部数据比如struct,需要自己定义后,编译器才能认识),malloc/free 无法满足动态创建对象的要求。

       我们希望在创建对象的时候,不仅开辟了内存,同时也完成了构造函数。对象在消亡时,也要自动执行析构函数。而由于 malloc/free是库函数,不在编译器的控制权限之内,也就不能自动执行构造函数与析构函数。所以,在c++中需要一个能完成动态内存分配和初始化工作的运算符new,以及一个能完成清理和释放内存工作的运算符delete。

为什么库函数不在编译器的控制范围内?

库函数是已经编译好的代码,编译器不会再去编译检查,由链接器将库与用户写的代码合成obj文件。而运算符,编译器在编译的时候,可以进行判定与操作。

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/938248.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号