C++ 动态分配内存
Posted
技术标签:
【中文标题】C++ 动态分配内存【英文标题】:C++ dynamically allocated memory 【发布时间】:2012-01-20 04:52:45 【问题描述】:我不太明白动态分配内存的意义,我希望你们能让我更清楚。
首先,每次我们分配内存时,我们只需要一个指向该内存的指针。
int * dynInt = new int;
那么做我上面所做的和:
int someInt;
int* dynInt = &someInt;
据我了解,在这两种情况下,内存都是为 int 分配的,并且我们得到了指向该内存的指针。
那么两者之间有什么区别。什么时候一种方法优于另一种方法。
还有更多为什么我需要使用
来释放内存delete dynInt;
在第一种情况下,但不是在第二种情况下。
我的猜测是:
当为对象动态分配内存时,对象不会被初始化,而如果你在第二种情况下执行类似的操作,对象会被初始化。如果这是唯一的区别,那么除了动态分配内存更快之外,还有其他动机吗?
我们不需要在第二种情况下使用 delete 的原因是,对象已初始化的事实创建了某种自动销毁例程。
如果有人纠正我并为我澄清事情,这些只是猜测。
【问题讨论】:
如果您不确定,请遵循简单的规则:“永远不要使用指针;永远不要使用new
。”一旦你理解了手动管理对象生命周期的必要性,你就会知道什么时候打破这个规则。
嗯,我强烈建议您打开一本关于 C++ 的书。变量范围、动态内存管理通常在任何 C++ 初学者书籍的第 4 章或第 5 章中讨论。 @KerrekSB 我不会这么说.. 特别是在这种情况下,不确定性不是来自歧义,而是来自知识不足 - 如果您不确定,请确保您阅读足够,所以您确定。
动态分配内存通常较慢,初始化与此无关。
-1 这个问题没有显示任何研究工作,这本来是在 C++ 书籍或类似书籍中查找该主题
【参考方案1】:
这样创建的对象:
int foo;
具有自动存储持续时间 - 对象一直存在,直到变量 foo
超出范围。这意味着在您的第一个示例中,一旦 someInt
超出范围(例如,在函数末尾),dynInt
将是一个无效指针。
这样创建的对象:
int foo* = new int;
具有动态存储持续时间 - 对象在您明确调用 delete
之前一直存在。
对象的初始化是一个正交的概念;它与您使用的存储持续时间类型没有直接关系。有关初始化的更多信息,请参阅here。
【讨论】:
学究起来,你真的对内存一无所知,因为这取决于你的分配函数::operator new()
的实现。它是动态对象的 lifetime,即手动:object 一直存在,直到您 delete
它。 C++ 将内存分配和对象构造分开。
@KerrekSB:写这样的答案我总是有点害怕,因为我肯定会出错:)【参考方案2】:
如果你的程序应该让用户存储任意数量的整数会发生什么?然后你需要在运行时根据用户的输入决定分配多少个整数,所以这必须动态完成。
【讨论】:
【参考方案3】:区别在于存储时间。
具有自动存储持续时间的对象是您的“正常”对象,它们会在定义它们的块的末尾自动超出范围。
像int someInt
一样创建它们;
您可能听说过它们作为“堆栈对象”,尽管我反对这个术语。
具有动态存储持续时间的对象具有“手动”生命周期;你必须自己用delete
销毁它们,然后用关键字new
创建它们。
您可能听说过它们是“堆对象”,但我也反对。
指针的使用实际上与它们中的任何一个都不严格相关。您可以拥有一个指向自动存储持续时间的对象的指针(您的第二个示例),您可以拥有一个指向动态存储持续时间的对象的指针(您的第一个示例)。
但您很少需要指向自动对象的指针,因为:
-
你没有“默认”;
该对象不会持续很长时间,因此您可以做这样一个指针。
相比之下,动态对象通常通过指针访问,仅仅是因为语法接近于强制执行它。 new
返回一个指针供您使用,您必须将指针传递给delete
,并且(除了使用引用)实际上没有其他方法可以访问该对象。它存在于“外面”的动态云中, 不在本地范围内。
正因为如此,指针的使用有时会与动态存储的使用混淆,但实际上前者与后者没有因果关系。
【讨论】:
在调用一个应该改变其参数的函数(并且可能使用它的返回值来指示成功)时,指向自动变量的指针可能很有用。 @TamásSzelei:在这种情况下,您应该使用参考。 @TamásSzelei:嗯,没错。不过,这是非常本地化的用法。我想我说的是获取指针并存储它,而不是在单个表达式中使用临时变量 @BjörnPollex 你是对的,出于某种原因,我认为这是一个 C 问题(因此在我的回答中使用了 malloc)。【参考方案4】:对于单个整数,仅当您需要在例如从函数返回之后保留该值时才有意义。如果您按照您所说的那样声明someInt
,那么它一旦超出范围就会失效。
但是,一般来说动态分配的用途更大。您的程序在分配之前不知道很多事情,并且取决于输入。例如,您的程序需要读取一个图像文件。那个图片文件有多大?我们可以说我们将它存储在这样的数组中:
unsigned char data[1000000];
但这仅适用于图像大小小于或等于 1000000 字节的情况,并且对于较小的图像也会造成浪费。相反,我们可以动态分配内存:
unsigned char* data = new unsigned char[file_size];
这里,file_size
是在运行时确定的。你不可能在编译的时候告诉这个值。
【讨论】:
你不能这样做:“unsigned char data[file_size];”? 不,你不能。好吧,一些 C 编译器可能会接受它,但这仍然是动态分配,而不是标准的 C(89) 或 C++;见VLA。 ISO C99 支持 VLA。【参考方案5】:当你在 C++ 中使用 new
时,内存是通过 malloc
分配的,它调用 sbrk
系统调用(或类似的)本身。因此,除了操作系统之外,没有人知道请求的大小。因此,您将不得不使用delete
(它调用free
,它再次转到sbrk
)将内存还给系统。否则会出现内存泄漏。
现在,当涉及到第二种情况时,编译器已经知道分配的内存大小。也就是说,在您的情况下,一个int
的大小。设置指向此int
地址的指针不会改变所需内存的任何知识。或者换句话说:编译器能够处理内存的释放。在new
的第一种情况下,这是不可能的。
除此之外:new
和 malloc
不需要精确分配请求的大小,这使事情变得有点复杂。
编辑
两个更常见的短语:第一种情况也称为静态内存分配(由编译器完成),第二种情况是指动态内存分配(由运行时系统完成)。
【讨论】:
【参考方案6】:阅读更多关于dynamic memory allocation和garbage collection的信息
您确实需要阅读一本好的 C 或 C++ 编程书籍。
详细解释会花费很多时间。
堆是发生动态分配的内存(在 C++ 中使用 new
或在 C 中使用 malloc
)。有system calls 参与了堆的增长和收缩。在 Linux 上,它们是mmap & munmap(用于实现malloc
和new
等...)。
您可以多次调用分配原语。因此,您可以将int *p = new int;
放入循环中,并在每次循环时获取一个新位置!
不要忘记释放内存(在 C++ 中使用 delete
或在 C 中使用 free
)。否则,你会得到一个memory leak - 一种顽皮的错误 -。在 Linux 上,valgrind 有助于捕获它们。
【讨论】:
【参考方案7】:简而言之,动态分配对象的生命周期由您控制,而不是由语言控制。这允许您让它在需要的时候一直存在(而不是范围的结束),这可能由只能在运行时计算的条件决定。
此外,动态内存通常更“可扩展” - 即与基于堆栈的分配相比,您可以分配更多和/或更大的对象。
分配本质上是“标记”一块内存,因此不能在同一空间中分配其他对象。取消分配“取消标记”那块内存,以便可以将其重用于以后的分配。如果您在不再需要内存后未能释放内存,则会出现称为“内存泄漏”的情况 - 您的程序正在占用它不再需要的内存,导致可能无法分配新内存(由于缺乏空闲内存),而且通常会给系统带来不必要的压力。
【讨论】:
【参考方案8】:您的程序在启动时会获得初始内存块。此内存称为堆栈。如今,该数量通常在 2MB 左右。
您的程序可以向操作系统请求额外的内存。这称为动态内存分配。这会在 free store(C++ 术语)或 heap(C 术语)上分配内存。您可以请求系统愿意提供的内存(数 GB)。
在栈上分配变量的语法如下:
int a; // allocate on the stack
// automatic cleanup on scope exit
使用空闲存储中的内存分配变量的语法如下所示:
int * a = new int; // ask OS memory for storing an int
delete a; // user is responsible for deleting the object
回答您的问题:
什么时候一种方法优于另一种方法。
-
通常首选堆栈分配。
当您需要使用其基类型存储多态对象时需要动态分配。
始终使用智能指针自动删除:
C++03:
boost::scoped_ptr
、boost::shared_ptr
或 std::auto_ptr
。
C++11:std::unique_ptr
或 std::shared_ptr
。
例如:
// stack allocation (safe)
Circle c;
// heap allocation (unsafe)
Shape * shape = new Circle;
delete shape;
// heap allocation with smart pointers (safe)
std::unique_ptr<Shape> shape(new Circle);
还有为什么我需要在第一种情况下释放内存,而在第二种情况下不需要。
正如我上面提到的,堆栈分配的变量会在范围退出时自动释放。 请注意,您不能删除堆栈内存。这样做会不可避免地导致您的应用程序崩溃。
【讨论】:
这个答案比其他答案要好得多,因为它实际上解释了在哪种情况下使用哪个内存分配以上是关于C++ 动态分配内存的主要内容,如果未能解决你的问题,请参考以下文章