【问题标题】:Question Regarding Dynamic memory allocation关于动态内存分配的问题
【发布时间】:2011-03-24 12:40:42
【问题描述】:

我对堆栈和动态内存分配有疑问。

1) 内核是在运行时动态确定堆栈的大小还是在加载时间之前设置大小?如果堆栈大小是动态分配的,如何发生堆栈溢出(因为如果堆栈大小超出限制,页面处理程序将分配空间来增长堆栈)。另外,如果动态分配,堆栈如何从高地址增长到低地址(因为动态分配的存储空间总是虚拟地址递增?)

2) 此外,如果内存是使用 malloc 动态分配的,那么数据区域的大小会增长吗?

感谢和问候,

鼠标。

【问题讨论】:

    标签: c unix


    【解决方案1】:

    堆栈大小

    通常,每个线程在创建线程时都有一个固定的堆栈。运行 ulimit -a 以查看系统的默认堆栈大小。在我的系统上,它是 8 MiB。当您创建新线程时,您可以为它们提供更小或更大的堆栈(请参阅pthread_attr_setstacksize)。

    当堆栈超过 8 MiB 时,程序将写入无效的内存位置并崩溃。内核确保堆栈旁边的内存位置都是无效的,以确保程序在堆栈溢出时崩溃。

    您可能认为固定大小是一种浪费,但 8 MiB 是虚拟内存,而不是物理内存。区别很重要,见下文。

    Malloc

    在 Unix 系统上,内存分配有两层。用户空间层是malloc(和callocreallocfree)。这只是 C 库的一部分,可以用您自己的代码替换——Firefox 会这样做,并且许多编程语言使用它们自己的分配方案而不是 malloc。各种malloc 实现是跨平台的。

    下层是mmap(和sbrk)。 mmap 函数是一个改变程序地址空间的系统调用。它可以做的一件事就是将新的匿名私有页面添加到程序的内存中。

    malloc 的目的是使用mmap(或sbrk)从内核获取大量虚拟内存,并为您的程序有效地划分它们。 mmap 系统调用仅适用于 4 KiB 的倍数(在大多数系统上)。

    内存:虚拟与真实

    请记住,mmap 返回的堆栈和所有内存只是虚拟内存,而不是物理 RAM。在您实际使用它之前,内核不会为您的进程分配物理 RAM。

    当您从内核获取匿名内存时,无论是在堆上还是在堆栈上,它都会被零填充。然而,内核并没有为您提供数百页预填充零的物理 RAM,而是使所有虚拟内存共享一个物理 RAM 页。虚拟内存被标记为只读。一旦你写它,CPU就会抛出一个异常,将控制权转移给内核,内核为你的程序分配一个新的、可写的、归零的页面。

    这解释了原因:

    • callocmalloc + memset 快(因为 calloc 知道 mmap 的页面已预置零,而 memset 强制分配物理 RAM)
    • 您可以分配比 RAM + 交换组合更多的内存(因为在写入之前不会使用它)

    【讨论】:

    • @Dietrich 非常感谢您的回复。所以每当 malloc 使用 sbrk 或 mmap 时,内存会附加到哪个区域?(数据、堆栈或文本)。应该是数据吧?
    • 有趣,但其中大部分仅适用于 Unix。
    • @Steven Windows 不区分区域?通常它是在可执行文件中完成的(Windows 中的 PE)。因此系统可以施加限制(例如:将文本或代码标记为只读,或者如果系统超出特定区域则给出异常)
    • Windows 在细节上有所不同。没有mmapsbrk,但是有内存映射。
    • @Steven:问题被标记为“Unix”,因此是 Unix 特定的答案。 @mousey:您的可执行文件的部分被映射到虚拟内存中,当您调用“mmap”时,您正在创建新的映射,而不是扩展旧的映射。
    【解决方案2】:

    1) 这有点取决于操作系统,但一个典型的方案是操作系统为您提供一页虚拟内存(页面通常为 4 KB)用于堆栈,然后立即将虚拟内存页面标记为“保护页”。这是一个特殊标志,当应用程序尝试写入时会触发低级异常。

    操作系统会在您尝试写入保护页面时处理异常(当您将堆栈增长到超过初始分配大小时会发生这种情况),为您分配该页面(即,将其映射到物理内存页面)并重新运行您的程序从发生错误写入的指令开始。这次它会工作,因为页面已经被真实内存支持。

    超过某个点(通常为 1 MB),操作系统将停止执行此操作并触发堆栈溢出异常。这只是因为这些通常表示程序错误,而真正需要巨大堆栈的代码可以为堆上的堆栈数据分配内存。

    2) 数据段并没有真正“增长”。现代程序具有固定的虚拟内存地址空间。 malloc() 使用某种方案来分割这个空间,并用真实的物理内存返回它的一部分。

    我认为您的两个问题都暗示您希望更好地了解操作系统如何为您的程序提供物理内存。现代系统中的关键概念是虚拟内存。 Wikipedia's page on virtual memory 是一个很好的起点。

    如果您想发展详细的知识,操作系统教科书将是一个不错的起点。最好是比我在大学学习操作系统课程时所拥有的更好:)

    【讨论】:

    • 非常感谢您的回复。另外我正在阅读一本操作系统书籍,它说 malloc 在交换空间上分配空间,所以我以不同的方式提出了这个问题。
    • "malloc" 不会将物理内存映射到您的进程中,相反,一旦您写入 malloc 返回的内存,内核就会这样做。您可以自己试验一下:即使您 malloc 数百兆,使用的物理 RAM 量也不会增加。
    • @Dietrich 所以它应该映射到我的虚拟地址吧?其他明智的程序不起作用!
    • @mousey:mmap 通常不会将物理 RAM 映射到您的虚拟地址。如果您写入程序,它仅记录内核将物理 RAM 映射到程序中的“承诺”。如果您从不使用虚拟内存,内核将不会分配物理 RAM 来支持它。
    【解决方案3】:

    1) 动态内存分配来自堆,而不是堆栈。堆栈是每个线程的内存块,它还在函数调用期间处理局部变量和返回地址。堆只是从操作系统分配的一个或多个内存块,C RTL 根据需要对其进行细分以满足 malloc 调用。

    至于堆栈,它通常具有固定大小,因为在没有无限递归的程序中,您应该只需要有限的数量。如果你继续递归,你会溢出,这是一件好事,因为它是一个干净的失败。至于内存增长,那是CPU的实现细节。

    2) 不一定。只要操作系统当前分配的内存足够,malloc 就会使用它。一旦它消失,它可能会导致额外的分配。

    我知道您想在这里获得更多详细信息,但我不确定什么有用。我可以讨论堆通常如何实现为空闲块的链接列表,其中包含定义其大小的 arena 标头,或者一些系统如何使用固定大小的块进行小分配以限制碎片,甚至当没有单个内存气泡时如何合并找到足够大的块。但是,所有这些都是实施细节,很可能不适用于您的情况,至少不完全适用。

    【讨论】:

    • 1) 我不是在问 C 动态内存分配!问题是关于内核如何分配堆栈。内核如何知道堆栈的大小! 2)你能更详细地解释一下吗?
    • 1) 通常的方法是为线程分配一个固定的内存块用作其堆栈。
    • 对于每个线程,堆栈的大小是否相同?这是浪费空间吧?
    • 并非如此,这取决于操作系统。例如,Windows 授予线程 1M 的地址空间。但是,它仅以 4K 块的形式分配,因此超出该范围的未使用内存范围永远不会被任何内存备份。
    • 内存可以在栈上动态分配。请参阅alloca,或可变长度数组。只是通常不会这样做。
    猜你喜欢
    • 2011-01-19
    • 2022-01-22
    • 1970-01-01
    • 2021-08-27
    • 1970-01-01
    • 2012-05-02
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多