当我可以从静态实现相同的目标时,为啥我需要使用动态内存分配?

Posted

技术标签:

【中文标题】当我可以从静态实现相同的目标时,为啥我需要使用动态内存分配?【英文标题】:Why do I need to use dynamic memory allocation when I can achieve the same from static?当我可以从静态实现相同的目标时,为什么我需要使用动态内存分配? 【发布时间】:2021-02-02 03:32:48 【问题描述】:

让我举个例子来解释我的问题,

案例一

#include<iostream>
using namespace std;
int main()
   int n;
   cin>>n;
   int a[n];
   for(int i=0;i<n;i++)
       cin>>a[i];

案例二

#include<iostream>
using namespace std;
int main()
   int n;
   cin>>n;
   int *a = new int[n];
   for(int i=0;i<n;i++)
       cin>>a[i];

如果我错了,请纠正我,据我了解,Case I属于静态内存分配域,Case II属于动态内存分配域。因此,如果我能够通过静态内存分配实现相同的功能,为什么要使用动态。

在上述两种情况下,我都能够实现相同的功能,但是为什么 Case I 被认为不好,而 Case II 是正确的方法。

这两个代码的唯一区别是第 6 行。

【问题讨论】:

不行,情况1是栈内存分配,还有is a non-standard g++ extension,所以最好不要用,IMO。 Case I 不被认为是“坏的”。它根本行不通。编译器不会理解你的。该语言本可以设计为为您进行动态分配,但他们却选择强制您使动态分配更加明确。 尝试编译两者,输入100000000,看看哪个崩溃。 @Yksisarvinen 我尝试了两个输入大小为 100000000,case II 运行良好,但 case I 崩溃了。我确信这是因为在 case I 中我们从堆栈中获取内存,这是一种稀缺资源,而 case II 使用大量可用的堆内存。跨度> 没错。有两件事需要考虑:便携性和可用内存。对于便携性问题,请参阅下面的答案。案例 I 是非标准的,只有某些编译器接受它。如果您不关心这一点,因为您确信您的代码只会使用一个编译器,那么您的下一个问题是这两个区域的可用内存。堆栈是否足以供您使用?根据这些,您可以选择适合您的解决方案。 【参考方案1】:

案例一属于静态内存分配域,案例二属于动态内存分配域。

这个假设是错误的。您正在使用的非标准功能与这样的 sn-p;

int n;

// determin n at runtime ...

int a[n];

被称为 VLA(可变长度数组)(有关更多详细信息,请参阅 this thread),并且是一种隐藏内存分配的有争议的方式(可能在堆栈上,请参阅 @André 的评论)并最终以方便的语法进行清理。

请注意,如果没有非标准 VLA 扩展,当编译时不知道最大数组维度时,您将无法使用堆栈空间中的数组。工作示例:

#include <array>

constexpr std::size_t N = 42; // known at compile time

std::array<int, N> data; // allocated on the stack

【讨论】:

您可能应该说“当 最大 数组维度未知时”。静态分配最大值并仅使用需要的部分是很常见的。 轻微吹毛求疵:vla 不会“隐藏动态内存分配”。对于 VLA,它通常位于堆栈中的某个位置,更像是一个可变大小的堆栈段。例如:***.com/q/31199566/417197【参考方案2】:

案例 1 执行"static" memory allocation,而是memory allocation "on stack"。这是variable length array。

有多种原因:

可变长度数组是一个编译器扩展。它们不是 C++ 的一部分。

可变长度数组没有错误处理。向用户转发任何有意义的错误消息是不可能的,而且调试此类程序非常困难。通常,该过程只会显示不友好的“分段错误”错误消息。

分配的最大内存将非常非常小,并且取决于代码的其他部分(使调试非常困难)。大多数Linux将堆栈限制设置为8Mb。分配更多将不会出错,而是当写入超过该阈值的内存位置时,进程将收到分段错误信号。您始终可以为您的进程设置更大的堆栈限制。

内存必须在块的末尾被释放。不可能从函数返回这样的内存并在其范围之外使用它,这使得它对于大多数使用动态内存的应用程序来说毫无用处。

【讨论】:

关于“操作系统更难管理分配的内存”:什么?操作系统不在乎。堆栈中的页面与其他页面一样是虚拟内存。它们可以单独交换到磁盘,也可以不交换。 Pages in the stack are virtual memory just like other pages我不知道。 好的,这是一个有趣的事实。内存管理可用于防止堆栈中的某些地址/指针错误。堆栈可能是 8 MiB,但到目前为止堆栈指针可能只有 1 MiB,系统可能知道分配了 8 MiB 的虚拟地址空间,但到目前为止只映射了 1 MiB 使用的部分。当进程尝试超过 1 MiB 的内存访问时,它会导致硬件陷阱,操作系统可以查看它来决定做什么。如果是读访问,操作系统可以说这是一个错误,内存没有被初始化,…… ... 它可以拒绝映射内存并向进程传递信号。如果是写访问,操作系统可以查看它在哪里。如果它只是超过 1 MiB,系统可以说,好吧,你正在增加堆栈,我将映射更多内存并让进程继续。如果超过 1 MiB,系统会说,哇,那是一个奇怪的跳转,你一定是搞错了,我不会映射内存,但会向进程发送信号。 VAX/VMS 曾经具有后一个功能:如果您在使用堆栈时尝试跳得太远,而不是以“正常”数量增长堆栈帧,则进程会崩溃。这成为支持可变长度数组的问题,其中有人试图在堆栈上创建一个大数组并开始写入其中的某些部分。必须修改编译器,以便在创建大型可变长度数组时,编译器生成对每个页面中一个元素的令牌写访问,以使堆栈以操作系统的速度增长接受。【参考方案3】:

正如@lubgr 解释的那样,不可能分配在编译时未确定的静态内存(在堆栈中)。 所以如果你想在运行时确定内存,你应该使用动态内存分配(堆)。

此外,正如@Jeff Hill 在Heap vs Stack 帖子中所解释的那样,堆大小在运行时是动态的,而堆栈大小是静态的(因此,即使可以在堆栈中分配运行时变量内存,有时您的应用程序也会面临堆栈溢出)。

另一个不同的是速度; Stack 比 Heap 快(因为它们的访问模式)

【讨论】:

是自动内存,不是静态的,“不可能”应该是“C++标准不支持”。当使用支持它的编译器时,可以通过编译器扩展来实现。此外,“堆”是用词不当;用于管理动态内存的内存结构不一定是堆。

以上是关于当我可以从静态实现相同的目标时,为啥我需要使用动态内存分配?的主要内容,如果未能解决你的问题,请参考以下文章

代理模式

动态代理_基础版

当我添加一个按钮时,为啥动态文本会消失?

java代理模式

静态代理与动态代理

如果存在 VLA,为啥仍然需要 malloc? [复制]