什么是 C++ 中的动态内存分配?
Posted
技术标签:
【中文标题】什么是 C++ 中的动态内存分配?【英文标题】:What is Dynamic Memory Allocation in C++? 【发布时间】:2013-01-12 12:49:15 【问题描述】:我正在学习 C++ 中的动态内存分配,并提到了关键字 new
和 new[]
。
据说可以让用户在运行时指定内存分配的大小,而不像在源代码中简单地声明一个具有固定大小的变量或数组。
我不明白这个概念。它是如何工作的?我只需要澄清一下这个想法,举个例子会很有帮助!
【问题讨论】:
我建议阅读这个:***.com/questions/8839943/… 是的,这几乎是重复的。 了解动态内存分配的一个好方法是实现自己的分配器。从数组分配内存。这就是我们在学校所做的。 【参考方案1】:我看过很多关于 C++ 内存分配的帖子,关于“new operator”与“operator new”的问题,关于new int(100)
vs new int[100]
的问题,关于内存初始化的问题......我认为应该有一个一劳永逸地总结一切的答案,我选择这个问题来写这个总结。它是关于动态内存分配的,ie 在运行时在堆上分配。我还提供了一个summary implementation(公共域)。
C 与 C++
动态内存分配的主要函数:
在C(标题<cstdlib>
)中,我们主要有malloc
和calloc
和free
。我不会谈论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 @ 发布)。
这些分配不初始化内存,特别是,它们不在分配的对象上调用默认构造函数。因此,您必须手动初始化所有元素,然后再使用delete
或delete[]
释放分配。
注意:我怎么强调都不应该让你自己使用它。但是,如果您应该使用它,请确保在此类分配上调用delete
或delete[]
时传递指向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++ 中的动态内存分配?的主要内容,如果未能解决你的问题,请参考以下文章