什么是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)个字节)。 