什么是 C++ 中的动态内存分配?

Posted

技术标签:

【中文标题】什么是 C++ 中的动态内存分配?【英文标题】:What is Dynamic Memory Allocation in C++? 【发布时间】:2013-01-12 12:49:15 【问题描述】:

我正在学习 C++ 中的动态内存分配,并提到了关键字 newnew[]。 据说可以让用户在运行时指定内存分配的大小,而不像在源代码中简单地声明一个具有固定大小的变量或数组。

我不明白这个概念。它是如何工作的?我只需要澄清一下这个想法,举个例子会很有帮助!

【问题讨论】:

我建议阅读这个:***.com/questions/8839943/… 是的,这几乎是重复的。 了解动态内存分配的一个好方法是实现自己的分配器。从数组分配内存。这就是我们在学校所做的。 【参考方案1】:

我看过很多关于 C++ 内存分配的帖子,关于“new operator”与“operator new”的问题,关于new int(100) vs new int[100] 的问题,关于内存初始化的问题......我认为应该有一个一劳永逸地总结一切的答案,我选择这个问题来写这个总结。它是关于动态内存分配的,ie 在运行时在堆上分配。我还提供了一个summary implementation(公共域)。


C 与 C++

动态内存分配的主要函数:

在C(标题<cstdlib>)中,我们主要有malloccallocfree。我不会谈论realloc。 在 C++ 中(标题 <new>),我们有: 带有初始化参数的模板单对象分配: new T( args ) new (std::nothrow) T( args ) delete ( T* ) 具有默认初始化的模板多对象分配: new T[ size_t ] new (std::nothrow) T[ size_t ] delete[] ( T* ) 没有为单个或多个对象分配的模板内存初始化: new (void*) T( args ) new (void*) T[ size_t ] 内部新表达式: 原始内存分配::operator new( size_t ); 原始内存分配无异常::operator new( size_t, std::nothrow ); 未分配的原始内存初始化::operator new( size_t, ptr )

请查看this post 进行简明比较。


旧版 C 动态分配

要点:完全类型擦除(void* 指针),因此无构造/破坏,以字节为单位指定大小(通常使用sizeof)。

malloc( size_t ) 根本不初始化内存(原始内存包含垃圾,总是在使用前手动初始化)。 calloc( size_t, size_t ) 将所有位初始化为 0(开销很小,但对 POD 数字类型很有用)。任何分配的内存都应使用free ONLY 释放。

类实例的构造/销毁应该手动完成 使用/内存释放之前。


C++ 动态分配

要点:由于相似的语法做不同的事情而令人困惑,all delete-statements 调用析构函数,all delete-statements 采用全类型指针,一些 new-statements 返回全类型指针,一些 new-statements 调用一些构造函数。

警告:如下所示,new 可以是关键字函数。为了avoid confusions,最好不要谈论“新操作员”和/或“新操作员”。我将任何包含new 作为函数或关键字的有效语句称为“new-statements”。人们还谈论“new-expressions”,其中new 是关键字而不是函数。

原始内存分配(无初始化)

不要自己使用它。这是由 new-expressions 内部使用的(见下文)。

::operator new( size_t )::operator new( size_t, std::nothrow ) 采用字节大小,如果成功则返回 void*。 如果失败,前者抛出异常std::bad_alloc,后者返回NULL。 将::operator new( sizeof(T) ) 用于T 类型的单个 对象(和delete 用于释放),::operator new( n*sizeof(T) ) 用于多个 对象(和@987654331 @ 发布)。

这些分配初始化内存,特别是,它们在分配的对象上调用默认构造函数。因此,您必须手动初始化所​​有元素,然后再使用deletedelete[] 释放分配。

注意:我怎么强调都不应该让你自己使用它。但是,如果您应该使用它,请确保在此类分配上调用deletedelete[] 时传递指向void 的指针而不是类型化指针(始终在手动初始化之后)。我亲身经历过使用某些编译器的非 POD 类型的运行时错误(可能是我的错误)。

原始内存初始化(无分配)

不要自己使用它。这是由 new-expressions 内部使用的(见下文)。 在下文中,我假设void *ptr = ::operator new( n*sizeof(T) ) 用于某些类型T 和大小n

然后::operator new( n*sizeof(T), (T*) ptr )使用默认构造函数T::T()ptr开始初始化n类型的元素T。这里没有没有分配,只是使用默认构造函数进行初始化。

单对象分配和初始化

new T( args ) 使用构造函数T::T( args )T 类型的单个对象分配内存。默认构造函数不会被调用除非 参数被省略(即new T() 甚至new T)。失败时抛出异常 std::bad_alloc。 与new (std::nothrow) T( args ) 相同,只是在失败时返回NULL。 使用delete调用析构函数T::~T(),释放对应的内存。

多对象分配和初始化

new T[n] 使用默认构造函数为T 类型的n 对象分配内存。失败时抛出异常 std::bad_alloc。 与new (std::nothrow) T[n] 相同,但在失败时返回NULL。 使用delete[]为每个元素调用析构函数T::~T()并释放相应的内存。

内存初始化(又名“新放置”)

这里没有分配。不管如何分配:

new (ptr) T(args) 在存储在 ptr 的内存上调用构造函数 T::T(args)。除非省略参数,否则不会调用默认构造函数。 new (ptr) T[n]n 类型的 T 对象上调用默认构造函数 T::T(),该对象从 ptr 存储到 ptr+n(即 n*sizeof(T) 字节)。

相关帖子

简洁对比new/delete vs malloc/free 更啰嗦Malloc vs new,看@Flexo的回答 New operator vs operator new,不要使用这些术语来避免混淆

【讨论】:

【参考方案2】:

所以,如果你想要一个由 10 个整数组成的数组,你会这样写:

int arr[10]; 

但是如果你想做这样的事情怎么办;

cout << "How many?";
cin >> num;

int arr[num];

嗯,C++ 语言不允许这样做。相反,您必须这样做:

int *arr = new int[num]; 

创建你的数组。稍后您必须[1] 使用:

delete [] arr; 

释放内存。

那么,这是如何工作的?当您调用 new 时,C++ 运行时库 [构成 C++ 基础的您不必编写的代码] 将计算出num 整数占用了多少空间,并为此在内存中找到一些空间。我不会详细介绍“你如何找到一些记忆”。现在,相信我,有一些可用的内存可以用来存储一些整数。

当您稍后调用delete 时,相同的内存将返回给它来自的内存“池”或“堆”。

当然,如果您有一台内存为 256 MB 的机器,并且您尝试请求空间来存储 2.5 亿个整数,请记住整数占用超过一个字节,它不会解决 - 这里没有“魔法” - 内存仍然受限于机器中可用的内存量......你有权在程序中确定它何时运行,你需要多少内存,而不是而不是必须决定何时编写程序。

编辑:通常最好使用已经存在的“容器-”和“包装器类”来“隐藏”任何内存分配,这对于这个目的非常有用。例如:

 std::vector<int> arr;

可以作为整数的变量存储,您不必担心释放内存,甚至在将它们存储在那里之前就知道需要多少。

 std::shared_ptr<int> arr = new int[num]; 

是另一种情况,当“shared_ptr”不再使用时[它在共享指针类中跟踪它,所以你永远不需要关心释放内存]。

[1] 如果您不想泄漏内存,那么泄漏内存是“不好的风格”。如果你这样做,不会让任何人开心。

【讨论】:

以上是关于什么是 C++ 中的动态内存分配?的主要内容,如果未能解决你的问题,请参考以下文章

在 C++ 中的 2D 动态内存分配数组中释放分配的内存

c和c ++中的动态内存分配和堆有啥区别

小白学习C++ 教程十六C++ 中的动态内存分配

C++程序的内存分区,为什么要使用动态内存,动态内存的分配使用释放

优化动态分配内存的变量

c++中字符串是如何分配内存的?