什么是C ++中的动态内存分配?
我正在学习C ++中的动态内存分配,并提到了new
和new[]
关键字。 据说使用户能够在运行时指定内存分配的大小,而不像在源代码中只声明一个固定大小的变量或数组。
我不明白这个概念。 它是如何工作的? 我只需要澄清一下这个想法,一个例子会很有帮助!
所以,如果你想要一个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 ++内存分配的帖子,关于“new operator”和“operator new”的问题,关于new int(100)
和new int[100]
问题,关于内存初始化的问题......我认为应该有一个清楚地总结所有事情的答案,我选择这个问题来写这个总结。 它是关于动态内存分配的,即在运行时在堆上进行分配。 我还提供了一个总结实施(公共领域)。
C vs C ++
动态内存分配的主要功能:
<cstdlib>
)中,我们主要有malloc
和calloc
并且是free
。 我不会谈论realloc
。 <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 )
。 请看这篇文章以进行简要比较。
传统C动态分配
要点 :完整的类型擦除( void*
指针),因此没有构造/破坏 ,以字节指定的大小(通常使用sizeof
)。
malloc( size_t )
根本不初始化内存(原始内存包含垃圾,在使用之前总是手动初始化)。 calloc( size_t, size_t )
将所有位初始化为0(开销很小,但对POD数字类型有用)。 任何分配的内存应该用发行free
ONLY。
构造/销毁类实例应该在使用之前/释放内存之前手动完成 。
C ++动态分配
要点 :因类似的语法做不同的事情而引起混淆, 所有的 delete
语句调用析构函数, 所有的 delete
语句都带有完全类型的指针, 一些 new
语句返回完全类型的指针, 一些 new
语句调用一些构造函数。
警告 :正如您将在下面看到的, new
可以是关键字OR函数。 最好不要谈论“新运营商”和/或“新运营商”,以避免混淆。 我将任何包含new
有效语句作为函数或关键字调用“ new
-statements”。 人们还谈论“ new
”, new
关键词而不是功能。
原始内存分配(无初始化)
不要自己使用这个。 这由新表达式在内部使用(见下文)。
::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) )
用于多个对象(和delete[]
以释放)。 这些分配不会初始化内存,特别是它们不会在分配的对象上调用默认构造函数。 因此,在使用delete
或delete[]
释放分配之前,您必须手动初始化所有元素 。
注意 :我无法强调你不应该自己使用它。 但是,如果您应该使用它,请确保在调用此类分配中的delete
或delete[]
(通常在手动初始化后)时,将指针传递给void
而不是类型指针。 我有一些编译器遇到了非POD类型的运行时错误(也许是我的错误)。
原始内存初始化(不分配)
不要自己使用这个。 这由新表达式在内部使用(见下文)。 在下面,我假设对于某些类型T
和大小为n
void *ptr = ::operator new( n*sizeof(T) )
。
然后::operator new( n*sizeof(T), (T*) ptr )
使用默认的构造函数T::T()
从ptr
开始,初始化类型为T
n
元素。 这里没有分配 ,只使用默认构造函数进行初始化。
单对象分配和初始化
new T( args )
分配和用于类型的单个对象初始化存储器T
使用构造T::T( args )
。 默认的构造函数不会被调用,除非参数被省略(即new T()
或甚至new T
)。 失败时抛出一个异常std::bad_alloc
。 new (std::nothrow) T( args )
除外,如果失败则返回NULL
。 delete
调用析构函数T::~T()
并释放相应的内存。 多对象分配和初始化
new T[n]
分配和用于初始化存储器n
类型的对象T
使用默认构造。 失败时抛出一个异常std::bad_alloc
。 new (std::nothrow) T[n]
只是它在失败的情况下返回NULL
。 delete[]
为每个元素调用析构函数T::~T()
并释放相应的内存。 内存初始化(又名“放置新的”)
这里没有分配。 不管分配的方式如何:
new (ptr) T(args)
T::T(args)
在存储在ptr
处的内存中调用构造函数T::T(args)
。 除非省略参数,否则不会调用默认构造函数。 new (ptr) T[n]
调用默认构造T::T()
上n
类型的对象T
从存储ptr
到ptr+n
(即, n*sizeof(T)
个字节)。