C++动态分配的内存

C++ dynamically allocated memory

本文关键字:内存 动态分配 C++      更新时间:2023-10-16

我不太明白动态分配内存的意义,我希望你们能为我说得更清楚。

首先,每次分配内存时,我们都会得到一个指向该内存的指针。

int * dynInt = new int;

那么做我上面做的事情和:

int someInt;
int* dynInt = &someInt;

据我了解,在这两种情况下,内存都是为 int 分配的,我们得到一个指向该内存的指针。

那么两者之间有什么区别。什么时候 一种方法优于另一种方法。

此外,为什么我需要释放内存

delete dynInt;

在第一种情况下,但在第二种情况下则不然。

我的猜测是:

  1. 为对象动态分配内存时,对象不会被初始化,而如果你像在第二种情况下那样做一些事情,对象就会被初始化。如果这是唯一的区别,那么除了动态分配内存更快这一事实之外,这背后是否有任何动机。

  2. 对于第二种情况,我们不需要使用 delete 的原因是,对象被初始化的事实创建了某种自动销毁例程。

这些只是猜测,如果有人纠正我并为我澄清事情,他们会喜欢它。

区别在于存储持续时间

  • 具有自动存储持续时间的对象是您的"普通"对象,它们在定义它们的块结束时会自动超出范围。

    int someInt一样创建它们;

    您可能听说过它们是"堆栈对象",尽管我反对这个术语。

  • 具有
  • 动态存储持续时间的对象具有某种"手动"生命周期; 您必须自己销毁它们 delete ,并使用关键字 new 创建它们。

    您可能听说过它们是"堆对象",尽管我也反对这样做。

指针的使用实际上与它们中的任何一个都没有严格关系。您可以有一个指向自动存储持续时间对象的指针(您的第二个示例(,也可以有一个指向动态存储持续时间对象的指针(您的第一个示例(。

但是,您很少需要指向自动对象的指针,因为:

  1. 您没有"默认">
  2. ;
  3. 该对象不会持续很长时间,因此您可以使用这样的指针很多事情。

相比之下,动态对象通常通过指针访问,这仅仅是因为语法接近于强制执行它。 new返回一个指针供您使用,您必须将指针传递给delete,并且(除了使用引用(实际上没有其他方法可以访问该对象。它生活在"外面"的动态云中,而不是坐在局部范围内。

正因为如此,指针的使用有时会与动态存储的使用混淆,但实际上前者与后者没有因果关系。

像这样创建的对象:

int foo;

具有自动存储持续时间 - 对象一直存在,直到变量foo超出范围。这意味着在您的第一个示例中,一旦someInt超出范围(例如,在函数末尾(,dynInt 将成为无效指针。

像这样创建的对象:

int foo* = new int;

具有动态存储持续时间 - 对象一直存在,直到您对其显式调用delete

对象的初始化是一个正交概念;它与您使用的存储持续时间类型没有直接关系。有关初始化的更多信息,请参阅此处。

  1. 程序在启动时获得初始内存块。此内存称为堆栈。如今,该数量通常在2MB左右。

  2. 程序可以要求操作系统提供额外的内存。这称为动态内存分配。这会在免费存储(C++术语(或(C 术语(上分配内存。您可以要求系统愿意提供的内存量(数千兆字节(。

在堆栈上分配变量的语法如下所示:

{
    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


要回答您的问题:

什么时候 一种方法优于另一种方法。

  1. 通常首选堆栈分配。
  2. 当您需要使用其基类型存储多态对象时,需要动态分配。
  3. 始终使用智能指针自动删除:
    • 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);

此外,为什么在第一种情况下我需要释放内存,但在第二种情况下不需要。

正如我上面提到的,堆栈分配的变量在范围退出时自动释放。请注意,不允许删除堆栈内存。这样做将不可避免地使您的应用程序崩溃。

对于单个整数,只有在您需要在例如从函数返回之后保留值时才有意义。如果你像你说的那样宣布someInt,一旦超出范围,它就会失效。

但是,一般来说,动态分配的用途更大。在分配之前,您的程序不知道很多事情,并且取决于输入。例如,程序需要读取图像文件。该图像文件有多大?我们可以说我们将其存储在这样的数组中:

unsigned char data[1000000];

但这仅在图像大小小于或等于 1000000 字节时才有效,并且对于较小的图像也会浪费。相反,我们可以动态分配内存:

unsigned char* data = new unsigned char[file_size];

在这里,file_size是在运行时确定的。在编译时,您不可能分辨出此值。

阅读有关动态内存分配和垃圾回收的更多信息

你真的需要阅读一本好的C或C++编程书籍

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

堆是发生动态分配(new在 C++ 中或malloc在 C 中(发生的内存。存在与增加和缩小堆有关的系统调用。在Linux上,它们是mmap和munmap(用于实现mallocnew等(。

您可以多次调用分配原语。因此,您可以将int *p = new int;放在循环中,并在每次循环时获得一个新的位置!

不要忘记释放内存(delete在C++中或free在C中(。否则,你会得到一个内存泄漏 - 一种顽皮的错误 - 。在Linux上,valgrind有助于抓住它们。

每当您在C++中使用new时,都会通过调用sbrk系统调用(或类似调用(本身的malloc分配内存。因此,除了操作系统之外,没有人知道请求的大小。因此,您必须使用delete(调用free再次转到sbrk(将内存返回给系统。否则,会出现内存泄漏。

现在,当涉及到第二种情况时,编译器已经知道分配的内存的大小。也就是说,在您的情况下,一int的大小.设置指向此int地址的指针不会更改所需内存的任何内容。或者换句话说:编译器能够处理释放内存的问题。在第一种情况下,new这是不可能的。

除此之外:new分别malloc不需要精确分配所需的大小,这使得事情变得更加复杂。

编辑

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

如果您的程序应该让用户存储任意数量的整数,会发生什么情况?然后,您需要在运行时根据用户的输入决定要分配多少整数,因此必须动态完成此操作。

简而言之,动态分配对象的生存期由您控制,而不是由语言控制。这允许您让它存活,只要它是必需的(而不是范围结束(,可能由只能在 run-rime 时计算的条件决定。

此外,动态内存通常更具"可扩展性",即与基于堆栈的分配相比,您可以分配更多和/或更大的对象。

分配

本质上是"标记"一块内存,因此不能在同一空间中分配其他对象。取消分配"取消标记"该内存片段,以便它可以在以后的分配中重复使用。如果在不再需要内存后未能释放内存,则会得到一种称为"内存泄漏"的情况 - 程序占用不再需要的内存,导致可能无法分配新内存(由于缺少可用内存(,并且通常会给系统带来不必要的压力。