【问题标题】:Does Malloc only use the heap if requested memory space is large?如果请求的内存空间很大,Malloc 是否只使用堆?
【发布时间】:2013-10-18 17:56:04
【问题描述】:

每当您研究进程的内存分配时,您通常会看到它的概述如下:

到目前为止一切顺利。

但是你有 sbrk() 系统调用,它允许程序更改其数据部分的上限,它也可以用于简单地检查限制在哪里 sbrk( 0)。使用该功能,我发现了以下模式:

模式 1 - 小型 malloc

我在我的 Linux 机器上运行以下程序:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int globalVar;

int main(){
        int localVar;
        int *ptr;

        printf("localVar address (i.e., stack) = %p\n",&localVar);
        printf("globalVar address (i.e., data section) = %p\n",&globalVar);
        printf("Limit of data section = %p\n",sbrk(0));

        ptr = malloc(sizeof(int)*1000);

        printf("ptr address (should be on stack)= %p\n",&ptr);
        printf("ptr points to: %p\n",ptr);
        printf("Limit of data section after malloc= %p\n",sbrk(0));

        return 0;
}

输出如下:

localVar address (i.e., stack) = 0xbfe34058
globalVar address (i.e., data section) = 0x804a024
Limit of data section = 0x91d9000
ptr address (should be on stack)= 0xbfe3405c
ptr points to: 0x91d9008
Limit of data section after malloc= 0x91fa000

正如您所见,分配的内存区域正好在旧数据段限制之上,并且在 malloc 之后该限制被向上推,因此分配的区域实际上位于新数据段内。

问题 1:这是否意味着小型 malloc 会在数据段分配内存而根本不使用堆?

模式 2 - 大 Malloc

如果在第 15 行增加请求的内存大小:

ptr = malloc(sizeof(int)*100000);

您现在将得到以下输出:

localVar address (i.e., stack) = 0xbf93ba68
globalVar address (i.e., data section) = 0x804a024
Limit of data section = 0x8b16000
ptr address (should be on stack)= 0xbf93ba6c
ptr points to: 0xb750b008
Limit of data section after malloc= 0x8b16000

正如您在此处看到的,数据段的限制没有改变,而是分配的内存区域位于间隙段的中间,在数据段和堆栈之间。

问题 2:这是否是实际使用堆的大型 malloc?

问题 3:对此行为有何解释?我发现它有点不安全,因为在第一个示例(小 malloc)中,即使在您释放分配的内存之后,您仍然可以使用指针并使用该内存而不会出现段错误,因为它将在您的数据中部分,这可能会导致难以检测到错误。

规格更新:Ubuntu 12.04,32 位,gcc 版本 4.6.3,Linux 内核 3.2.0-54-generic-pae。

更新 2:罗德里戈的回答解决了这个谜团。 This Wikipedia link 也有帮助。

【问题讨论】:

  • 所有这些“如果 Y 发生,X 会发生”问题都是理论上的,实际上无法回答,而无需提及具体的实现。什么Linux?哪个编译器?哪个标准库实现?什么 CPU?
  • @H2CO3,你是说你确定上述行为是依赖于实现的,而不是 Linux 内核的标准吗?因为如果这是 Linux 内核的标准,那么规格就不重要了,对吧?无论哪种方式,为了完整起见,我都将它们包括在内。
  • @H2CO3 我同意。尽管如此,我还是觉得这种行为很奇怪(不是吗?),所以让我们看看是否有人对此有更多线索。
  • 我的理解是malloc 对用户空间中的堆进行内存管理——根据需要从操作系统释放或请求大块内存(即试图减少昂贵的上下文切换)。我还认为 malloc 确实请求了可用于该操作系统/硬件的内存块。
  • 请记住,许多堆管理器会将非常大的分配(通常超过 16M 左右)放置在与其他“段”不同的“段”中,地址中的高位组明显不同.堆栈和堆位于不同的段中并不少见。上图非常简单明了(并且是一个很好的概念视图),但通常不能反映现实。

标签: c pointers memory-management malloc heap-memory


【解决方案1】:

首先,绝对确定发生了什么的唯一方法是阅读malloc 的源代码。或者更好的是,使用调试器逐步完成。

但无论如何,这是我对这些事情的理解:

  1. 系统调用sbrk()用来增加数据段的大小,可以。通常,您不会直接调用它,而是由malloc()实现调用,以增加堆可用的内存。
  2. 函数malloc() 不从操作系统分配内存。它只是将数据部分分成几部分并将这些部分分配给需要它们的任何人。您可以使用 free() 将一件作品标记为未使用并可重新分配。
  3. 第 2 点过于简单化了。至少 GCC 实现,对于大块,malloc() 使用带有私有、非文件支持选项的 mmap() 分配它们。因此,这些块在数据段之外。显然,在这样的块中调用free() 将调用munmap()

究竟什么是大块取决于许多细节。有关血腥细节,请参阅man mallopt

由此,您可以猜到当您访问已释放的内存时会发生什么:

  1. 如果块,内存仍然存在,所以如果你读什么都不会发生。如果你写它,你可能会破坏内部堆结构,或者它可能已经被重用,你可以破坏任何随机结构。
  2. 如果块很大,则内存已被取消映射,因此任何访问都会导致分段错误。除非在此期间分配了另一个大块(或另一个线程调用 mmap() 并且恰好使用了相同的地址范围。

澄清

术语数据部分根据上下文有两种不同的含义。

  1. 可执行文件的.data 部分(链接器观点)。它还可能包括.bss 甚至.rdata。对于没有任何意义的操作系统,它只是将程序的一部分映射到内存中,除了标志(只读、可执行...)之外几乎不关心它包含的内容。
  2. ,即每个进程都拥有的内存块,不会从可执行文件中读取,可以使用sbrk() 进行增长。

您可以使用以下打印简单程序的内存布局的命令 (cat) 看到:

$ cat /proc/self/maps
08048000-08053000 r-xp 00000000 00:0f 1821106    /usr/bin/cat
08053000-08054000 r--p 0000a000 00:0f 1821106    /usr/bin/cat
08054000-08055000 rw-p 0000b000 00:0f 1821106    /usr/bin/cat
09152000-09173000 rw-p 00000000 00:00 0          [heap]
b73df000-b75a5000 r--p 00000000 00:0f 2241249    /usr/lib/locale/locale-archive
b75a5000-b75a6000 rw-p 00000000 00:00 0 
b75a6000-b774f000 r-xp 00000000 00:0f 2240939    /usr/lib/libc-2.18.so
b774f000-b7750000 ---p 001a9000 00:0f 2240939    /usr/lib/libc-2.18.so
b7750000-b7752000 r--p 001a9000 00:0f 2240939    /usr/lib/libc-2.18.so
b7752000-b7753000 rw-p 001ab000 00:0f 2240939    /usr/lib/libc-2.18.so
b7753000-b7756000 rw-p 00000000 00:00 0 
b7781000-b7782000 rw-p 00000000 00:00 0 
b7782000-b7783000 r-xp 00000000 00:00 0          [vdso]
b7783000-b77a3000 r-xp 00000000 00:0f 2240927    /usr/lib/ld-2.18.so
b77a3000-b77a4000 r--p 0001f000 00:0f 2240927    /usr/lib/ld-2.18.so
b77a4000-b77a5000 rw-p 00020000 00:0f 2240927    /usr/lib/ld-2.18.so
bfba0000-bfbc1000 rw-p 00000000 00:00 0          [stack]

第一行是可执行代码(.text 部分)。

第二行是只读数据(.rdata 部分)和其他一些只读部分。

第三行是.data + .bss 和其他一些可写的部分。

第四行是堆!

下一行,有名字的是内存映射文件或共享对象。那些没有名字的可能是大块 malloc'ed 内存(或者可能是私有匿名 mmap,它们无法区分)。

最后一行是栈!

【讨论】:

  • 在第 1 点中,您说 sbrk() 用于增加“数据段”的大小,但是 malloc() 调用 sbrk() 函数来增加“堆可用的内存” ”。所以不清楚。您是说 sbrk() 作用于数据部分还是作用于堆?还是说从实际的角度来看它们是同一回事?
  • 另外,您是否同意可以说“堆”是您通过 mmap() 分配大块内存时,而对于小块,“堆”将不会被使用而是“数据部分”?
  • @DanielS:抱歉,我不是很清楚。请参阅我添加的示例。
  • @DanielS:不!堆是malloc用于小块的内存块,也是用于管理这些块的数据结构。 mmap() 仅使用未分配空间,用于管理这些空间的结构位于内核 VMM 中。另外,请注意堆结构完全在用户空间库代码中实现。
  • 明白了,现在说得通了。因此,当 sbrk() 的手册页谈论“数据部分”时,它实际上是在谈论“堆”,对吗? sbrk(0) 确实返回了堆顶?我确实对可执行文件数据部分感到困惑。现在有意义的是,要么分配在堆上的 malloc,要么如果它太大,则通过 mmpa() 分配。现在正确吗?
猜你喜欢
  • 1970-01-01
  • 2014-03-28
  • 1970-01-01
  • 2019-08-13
  • 1970-01-01
  • 2014-03-14
  • 1970-01-01
  • 1970-01-01
  • 2012-12-13
相关资源
最近更新 更多