c++中能否判断一个指针指向栈还是堆?

关注者
102
被浏览
4259

8 个回答

Linux的话,可以查看进程 $PID 的地址空间映射
% cat /proc/$PID/maps                      
00400000-0040c000 r-xp 00000000 08:09 3156310                            /usr/bin/cat
0060b000-0060c000 r--p 0000b000 08:09 3156310                            /usr/bin/cat
0060c000-0060d000 rw-p 0000c000 08:09 3156310                            /usr/bin/cat
01f73000-01f94000 rw-p 00000000 00:00 0                                  [heap]
7f4d25a72000-7f4d2604b000 r--p 00000000 08:09 3196782                    /usr/lib/locale/locale-archive
7f4d2604b000-7f4d261e4000 r-xp 00000000 08:09 3149082                    /usr/lib/libc-2.21.so
7f4d261e4000-7f4d263e4000 ---p 00199000 08:09 3149082                    /usr/lib/libc-2.21.so
7f4d263e4000-7f4d263e8000 r--p 00199000 08:09 3149082                    /usr/lib/libc-2.21.so
7f4d263e8000-7f4d263ea000 rw-p 0019d000 08:09 3149082                    /usr/lib/libc-2.21.so
7f4d263ea000-7f4d263ee000 rw-p 00000000 00:00 0 
7f4d263ee000-7f4d26410000 r-xp 00000000 08:09 3149136                    /usr/lib/ld-2.21.so
7f4d265d2000-7f4d265d5000 rw-p 00000000 00:00 0 
7f4d265ed000-7f4d2660f000 rw-p 00000000 00:00 0 
7f4d2660f000-7f4d26610000 r--p 00021000 08:09 3149136                    /usr/lib/ld-2.21.so
7f4d26610000-7f4d26611000 rw-p 00022000 08:09 3149136                    /usr/lib/ld-2.21.so
7f4d26611000-7f4d26612000 rw-p 00000000 00:00 0 
7ffd4f536000-7ffd4f559000 rw-p 00000000 00:00 0                          [stack]
7ffd4f5c9000-7ffd4f5cb000 r--p 00000000 00:00 0                          [vvar]
7ffd4f5cb000-7ffd4f5cd000 r-xp 00000000 00:00 0                          [vdso]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0                  [vsyscall]
kernel.org/doc/Document
[heap] = the heap of the program
[stack] = the stack of the main process
[stack:1001] = the stack of the thread with tid 1001
[vdso] = the "virtual dynamic shared object",
the kernel system call handler

@乌鸦 提到heap不一定都在brk下面。
glibc的void *malloc(size_t size)实现,在size较大时会使用mmap分配,否则在一块预先申请的arena里分配。多线程环境下可能有多个arena,其中第一个是[heap],应该是用brk()得到的,其他是mmap的,不查看malloc实现相关的数据结构很难和其他mmap的空间区分。
@RednaxelaFX 提到多线程下新建线程的栈都是[anon]。
/proc/$pid/maps 和 /proc/$pid/task/$tid/maps 有所区别。我在Arch Linux core/linux 3.19.3-3下发现 /proc/$pid/maps 会标出其他线程的栈:[stack:$tid],而 pmap -x 会把 [stack:$tid] 显示为 [anon],pmap -X 保留 maps 里的字样。

单线程环境下似乎heap对象指针值均小于program break,而栈对象指针值均大于:
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>

int main()
{
  void *heap = malloc(0xbeef);
  char stack[0x1337];
  printf("%d\n", heap < sbrk(0));
  printf("%d\n", (void *)stack < sbrk(0));
}
另外一个可能不是随时都管用的方法,https://gcc.gnu.org/onlinedocs/gcc/Local-Reg-Vars.html#Local-Reg-Vars 获取寄存器RSP的值 不过ESP/RSP不一定都小于所有局部变量的地址。
#include <stdio.h>
#include <stdlib.h>

int main()
{
  void *heap = malloc(0xbeef);
  char stack[0x1337];
  register void *rsp asm ("rsp");
  printf("%d\n", heap < rsp);
  printf("%d\n", (void *)stack < rsp);
}
其他 malloc 实现不知道是什么样。
需要注意的是指针指向的内存并不是非堆即栈的,比如函数指针,以及程序调用系统接口自己申请的虚拟内存等。C++语言本身没法做栈或堆的判断,针对特定平台的hack方法是有的。
一般线程的栈大小是固定值的,如果创建时不指定栈大小,此线程的栈指针之间的距离不会高于这个值,可以作为判断条件之一。
堆一般都会维护已分配内存的列表,可以遍历比较。但不同系统的堆实现差别很大,也没有通用的方法。