17

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

在此处输入图像描述

到目前为止,一切都很好。

但是您有 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:这是否意味着小型mallocs将在数据部分分配内存而根本不使用堆?

模式 2 - 大马洛克

如果在第 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:罗德里戈的回答解决了这个谜团。这个维基百科链接也有帮助。

4

1 回答 1

13

首先,绝对确定会发生什么的唯一方法是阅读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]

第一行是可执行代码(.textsection)。

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

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

第四行是堆!

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

最后一行是栈!

于 2013-10-10T20:08:10.573 回答