我假设您正在尝试在这里编写自己的堆分配器,并且从标签中假设您是在 Linux 中进行的。
SunEric 为您提供了一个有用的指示,说明您可能可以使用哪些内存,但是,您可以使用的内存是操作系统为您提供的内存. IE 要将内存放入您的进程,您需要调用操作系统将虚拟内存映射到进程空间(以及它后面的一些物理内存)。 malloc() 为您抽象了这个,并在 C 中实现了“堆”。它可以通过两种方式获取它的内存:
使用brk系统调用(映射到C库brk或sbrk)
将mmap 与MAP_ANON 一起使用(或更准确地说是底层系统调用mmap2)。
brk 是为堆分配内存的经典方式,通常当我们谈论“堆”时,我们指的是这种方式分配的内存(尽管brk 可用于分配堆以外的内存,并且堆项目可能存在于其他地方 - 见下文)。 Here 是对 brk 分配工作原理的一个很好的回答,对此我无法改进。内存使用的位置实际上是算术的结果。堆在加载时跟随程序的 BSS - 即 BSS 的值随着堆的扩展而增长,因此开始实际上是由操作系统和动态加载器决定的。因此,堆的结尾是由这个和堆的大小决定的(即你已经把它增长到了多大)。
mmap 不太明确。它需要一个addr 参数:
如果addr 是NULL,那么内核选择创建映射的地址;这是创建新映射的最便携的方法。如果addr 不是NULL,那么内核会将其作为映射放置位置的提示;在 Linux 上,映射将在附近的页面边界处创建。新映射的地址作为调用的结果返回。
因此,如果您使用mmap 为特定的堆项目获取空间(因为malloc 可能特别适用于大型对象),操作系统会选择它的位置,有或没有提示。如果您使用MAP_FIXED,它将为您提供确切的位置或失败。从这个意义上说,您的堆(或其中的项目)可能位于操作系统允许您映射内存的任何位置。
您问是否有一种可移植的方法来找出堆的开始和结束位置。可移植意味着一种语言,我假设是 C。关于brk 类型的堆,是的(相当可移植)。 man end 给:
姓名
etext, edata, end - 程序段结束
概要
extern etext;
extern edata;
extern end;
描述
这些符号的地址表示各个程序段的结束:
etext:这是文本段(程序代码)末尾之后的第一个地址。
edata:这是初始化数据段末尾之后的第一个地址。
end:这是未初始化数据段(也称为 BSS 段)末尾的第一个地址。
由于堆从加载时BSS 的末尾运行到运行时BSS 的顶部,一种方法是将加载时end 的值作为底部的开始当评估为堆的末尾时,堆的值和end 的值。这会忽略libc 本身和共享库可能会在调用main() 之前分配东西的事实。所以更保守的方法是说它是edata 和end 之间的区域,尽管严格来说这可能包括不在堆上的东西。
如果您的意思不是在 C 中,您需要使用类似的技术。取“程序中断”(即内存空间的顶部)并减去您为堆提供的最低地址。
如果您想查看任意进程的堆内存分配:
$ cat /proc/$$/maps | fgrep heap
01fe6000-02894000 rw-p 00000000 00:00 0 [heap]
将$$ 替换为您要检查的进程的PID。