【问题标题】:Any limitations or location differences between malloc and BSS?malloc 和 BSS 之间有什么限制或位置差异吗?
【发布时间】:2025-12-19 20:05:06
【问题描述】:

(为了这个问题,请忽略全局变量的优缺点。)

全局变量是否与动态分配的内存位于同一内存空间中?

例如,通常如果我有一个大型的数兆字节数据结构,我想在内存中使用,我 malloc() 我需要的数量并从那里使用结构指针。

我的问题是,这与将大型结构定义为全局结构之间有什么区别吗?我知道未初始化的全局变量的大小在对象 BSS 中定义,但实际上不会占用对象文件中的空间,因此从某种意义上说,它们是在运行时分配的。但是 BSS 定义的全局变量的大小是否有任何限制?它们是否以 malloc 以外的方式分配?

【问题讨论】:

  • 这一切都将高度依赖系统。
  • BSS 段中的全局变量在模块加载时一次性全部分配。如果您计划在程序的整个生命周期中使用全部或大部分,这很好,但否则可能会造成浪费。
  • 一切都是实现定义的。我知道在启动时从操作系统池中动态分配内存以将静态持续时间对象放在那里的实现。在这种情况下没有区别,因为使用了相同的分配机制。
  • 定义“内存空间”。归根结底,由于现代内核如何虚拟化所有内容,因此许多这些差异只是学术性的。值得注意的是,2020年的“大”并不意味着“数兆”。我会为使用超过 20% 的系统内存或 4GB+ 的东西保留该术语,当然,如果您在一些受限的嵌入式环境中除外。如果是这种情况,您将需要具体说明您正在使用的架构和操作系统(如果有的话)。
  • 如果这是您要的,它不在堆栈中。这仅适用于函数,其中包括 main()

标签: c allocation


【解决方案1】:

全局变量是否与动态分配的内存位于同一内存空间中?

是的,在大多数现代平台中,进程将内存视为单个平坦的内存空间(但它们通常无法访问所有实际内存;它是一个虚拟空间)。

我知道未初始化的全局变量的大小在对象 BSS 中定义,但实际上不会占用对象文件中的空间,因此从某种意义上说,它们是在运行时分配的。

一切都是在运行时“分配”的,而不仅仅是 BSS 区域。程序不随身携带分配的内存,而是使用一种文件格式来指定内存需要如何布局(包括内容、页面标志、对齐、位置、内容...)以及其他详细信息。

操作系统在加载程序时获取该信息并创建一个满足这些需求的进程,然后启动程序。

但是 BSS 定义的全局变量的大小是否有任何限制?

这取决于平台,但在大多数情况下,除非您想要请求不寻常的尺寸,否则您不会注意到限制。有关 Linux x86_64 的非常好的概述,请参阅 Nate 的回答。

它们是否以 malloc 以外的方式分配?

如上所述,即使在程序开始运行之前,操作系统也会使用程序中有关 BSS 的信息来映射一些用零填充的内存。

另一方面,malloc() 是一个标准 C 库调用,它使用操作系统提供的任何工具来动态请求内存。换句话说,当程序已经在运行时。

但两者最终都在程序所见的相同内存空间中(在大多数现代平台上)。

【讨论】:

    【解决方案2】:

    此答案适用于

    静态对象不使用malloc 分配(当然,您不能尝试free 它们)。当二进制文件为execed 时,内核会为它们分配内存,以及程序的其余代码和数据。这通常称为“加载时间”而不是“运行时间”,因为它发生在您的任何程序代码实际执行之前。唯一的区别是分配给.bss 的内存没有填充从二进制文件加载的数据。与程序的其余部分一样,它通常是按需分页的,因此(归零的)物理内存仅在实际读取或写入空间时才分配,并且可以稍后交换到磁盘。它或多或少等同于匿名mmap

    (我在这里使用“静态”一词是指存储持续时间,而不是范围,它同样适用于 C 中的全局变量和 static 变量。两者之间的内存位置存在一些差异- 独立代码,我将不赘述,但它不会实质性地改变下面的信息。)

    我想你知道malloc 的一般工作原理,以及它如何从操作系统获取内存(通过sbrk 或匿名mmap,通常是后者用于大型对象),所以我不会描述.

    您可能遇到的主要区别是,在默认编译器设置下,静态数据(包括 bss)被限制在 2GB 左右。 Linux 编译器工具链默认使用"small" code model;所有代码和静态数据都使用 32 位有符号 RIP 相对位移访问,因此任何两个这样的地址必须在 2 GB 范围内,因此实际上该限制适用于所有代码和静态数据的总大小。如果超过此限制,您的程序将无法编译或链接。 (事实上​​,在某些测试中,如果你有幸在数据末尾有一个比这更大的对象,那么它是可能的,但这在实际程序中不太可能实现。)

    您可以通过在编译时选择“中”或“大”代码模型来避免此限制,以便使用 64 位绝对地址,但代价是它们会导致代码更大且效率更低,可能会导致你的整个程序。因此,除非您有非常特殊的原因想要静态分配大对象,否则动态分配可能会更好。

    对于非常大的分配,如果可移植性不是必需的,您可能更喜欢直接使用匿名mmap,而不是malloc。这使您可以更好地控制何时将内存返回给操作系统(使用munmap),还可以让您利用mprotectmadvise、大页面等功能来更细粒度地控制MMU 和分页机制。

    然而,一般来说,记忆就是记忆,不管它是如何获得的。

    【讨论】:

      最近更新 更多