我对堆栈和动态内存分配有疑问。
1)内核是否在运行时动态确定堆栈的大小,或者在加载时间之前设置大小?如果堆栈大小是动态分配的,那么堆栈溢出是如何发生的(因为如果堆栈的大小超出限制,页面处理程序将分配空间来增加堆栈)。另外如果动态分配堆栈如何从较高地址增长到较低地址(因为动态分配存储的虚拟地址总是增加吗?)
2)如果使用malloc动态分配内存,数据区域的大小会增长吗?
谢谢&的问候,
mouseY的。
答案 0 :(得分:3)
1)它取决于操作系统,但典型的方案是操作系统为堆栈提供一页虚拟内存(页面通常为4 KB),然后在其后立即标记虚拟内存页面一个“警卫页面”。这是一个特殊标志,当应用程序尝试写入时会触发低级异常。
当您尝试写入保护页面时(当您将堆栈增加到初始分配大小时发生),操作系统处理异常,为您分配该页面(即将其映射到物理内存页面),然后重新运行你的程序来自发生错误写入的指令。这次它将工作,因为页面已经由实际内存支持。
超过某一点(通常为1 MB),操作系统将停止执行此操作并触发堆栈溢出异常。这只是因为它们通常表示程序错误,而真正需要大量堆栈的代码可以为堆栈中的堆栈数据分配内存。
2)数据段并没有真正“增长”。现代程序具有固定的虚拟内存地址空间。 malloc()使用一些方案来雕刻这个空间,并用真实的物理内存来支持它的部分。
我认为你的两个问题都暗示想要更好地理解操作系统如何为你的程序提供物理内存。现代系统中的关键概念是虚拟内存。 Wikipedia's page on virtual memory是一个很好的起点。
如果您想开发详细的知识,操作系统教科书将是一个很好的起点。最好是一个比我在大学学习OS课程时所拥有的更好的一个:)
答案 1 :(得分:1)
堆叠尺寸
通常,每个线程在创建线程时都有一个固定的堆栈。运行ulimit -a
以查看系统的默认堆栈大小。在我的系统上,它是8 MiB。创建新线程时,可以为它们提供更小或更大的堆栈(请参阅pthread_attr_setstacksize
)。
当堆栈增长超过8 MiB时,程序将写入无效的内存位置并崩溃。内核确保堆栈旁边的内存位置都是无效的,以确保程序在堆栈溢出时崩溃。
您可能认为固定大小是浪费,但8 MiB是虚拟内存而不是物理内存。差异很重要,见下文。
<强>的malloc 强>
在Unix系统上,内存分配有两层。用户空间图层为malloc
(以及calloc
,realloc
,free
)。这只是C库的一部分,可以用自己的代码替换 - Firefox会这样做,许多编程语言使用自己的分配方案而不是malloc
。各种malloc
实现是跨平台的。
下层是mmap
(和sbrk
)。 mmap
函数是一个系统调用,它可以改变程序的地址空间。它可以做的一件事是在程序的内存中添加新的匿名私有页面。
malloc
的目的是使用mmap
(或sbrk
)从内核获取大块虚拟内存,并为您的程序有效地划分它们。 mmap
系统调用仅适用于4 KiB的倍数(在大多数系统上)。
记忆:虚拟与真实
请记住,mmap
返回的堆栈和所有内存只是虚拟内存,而不是物理RAM。在您实际使用它之前,内核不会为您的进程分配物理RAM。
当您从内核获取匿名内存时,无论是在堆还是堆栈上,它都会填充零。但是,内核不是为您提供预先填充零的数百页物理RAM,而是使所有虚拟内存共享一页物理RAM。虚拟内存标记为只读。一旦你写入它,CPU就会抛出一个异常,将控制转移到内核,然后内核为你的程序分配一个新的,可写的,归零的页面。
这解释了原因:
calloc
比malloc
+ memset
快(因为calloc
知道mmap
'd页已预先置零,{{1} }强制分配物理RAM)答案 2 :(得分:0)
1)动态内存分配来自堆,而不是堆栈。堆栈是每线程内存块,它还处理局部变量并在函数调用期间返回地址。堆只是从OS分配的一个或多个内存块,C RTL根据需要细分以满足malloc调用。
对于堆栈,它通常具有固定大小,因为在没有无限递归的程序中,您应该只需要有限的数量。如果你继续递归,你会溢出,这是一件好事,因为它是一个彻头彻尾的失败。至于内存的增长,这是CPU的实现细节。
2)不一定。只要当前从OS分配的内存足够,malloc就会使用它。一旦消失,可能会导致额外的分配。
我知道你想要更多细节,但我不确定什么是有用的。我可以讨论如何将堆通常实现为具有定义其大小的竞技场标头的空闲块的链接列表,或者某些系统如何使用固定大小的块来进行小分配以限制碎片,或者甚至在没有单个时如何合并内存气泡找到足够大的块。但是,所有这些都是实现细节,可能在您的情况下不适用,至少不完全适用。