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 的第一种情况下,这是不可能的。

除此之外:newmalloc 不需要精确分配请求的大小,这使事情变得有点复杂。

编辑

两个更常见的短语:第一种情况也称为静态内存分配(由编译器完成),第二种情况是指动态内存分配(由运行时系统完成)。

【讨论】:

【参考方案6】:

阅读更多关于dynamic memory allocation和garbage collection的信息

您确实需要阅读一本好的 C 或 C++ 编程书籍

详细解释会花费很多时间。

堆是发生动态分配的内存(在 C++ 中使用 new 或在 C 中使用 malloc)。有system calls 参与了堆的增长和收缩。在 Linux 上,它们是mmap & munmap(用于实现mallocnew 等...)。

您可以多次调用分配原语。因此,您可以将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_ptrboost::shared_ptrstd::auto_ptr。 C++11:std::unique_ptrstd::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++ 动态分配内存的主要内容,如果未能解决你的问题,请参考以下文章

C++ 动态分配内存

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

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

优化动态分配内存的变量

C++ 动态内存分配(6种情况,好几个例子)

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