如何判断某个变量是堆还是栈分配的?

14

我想知道有没有一种方法可以确定一个变量是在堆栈上还是在堆上分配的。

考虑以下代码:

struct SomeStruct;

fn main() {
    let some_thing = Box::new(SomeStruct);
    println!("{:p}", some_thing);
    foo(&*some_thing);
}

fn foo (bar: &SomeStruct) {
    println!("{:p}", bar);
}

打印

0x1
0x1

然后

struct SomeStruct;

fn main() {
    let some_thing = &SomeStruct;
    println!("{:p}", some_thing);
    foo(some_thing);
}

fn foo (bar: &SomeStruct) {
    println!("{:p}", bar);
}

打印

0x10694dcc0
0x10694dcc0

我能看到堆分配版本的内存地址要短得多,但我不知道这是否是一种可靠的区分方法。我想知道是否有类似 std::foo::is_heap_allocated() 的东西。


2
首先浮现在脑海中的问题是“为什么?”。有了这个知识,会对哪种代码产生不同的影响呢? - Shepmaster
6
我不需要这段代码做其他事情,只是为了更好地了解这门语言 :) - Christoph
6
0x1是Rust分配器为零大小对象返回的虚拟地址,不在堆上。详见[heap.rs#L90](https://github.com/rust-lang/rust/blob/9ecc9896dedb426e3f4eb3d23dfc60192fe5275f/src/liballoc/heap.rs#L90)。 - Denilson Amorim
1
我有同样的问题。我想知道是因为我对底层的工作方式很感兴趣。例如,这个简单的例子似乎显示了几乎连续的地址 https://play.rust-lang.org/?gist=4d9e0b2e94fe179edf9baf961316665c&version=stable 。现在我想知道是否所有对堆的引用都占用堆栈空间,并且它正在给我堆引用的堆栈地址。 - MrMesees
@MrMesees 所有地址都是连续的,因为你只获取(并打印)堆栈地址:numdog 在堆栈上,zoo 是一个数组,所以它也在堆栈上,而 Vec 的“存储缓冲区”虽然是在堆上分配的,但你只打印了 Vec 结构本身的地址(一个指针、一个长度和一个容量的三元组),而这个结构体本身是在堆栈上的。 - Masklinn
@MrMesees 顺便提一下,虽然 Vec 类型有点像指针,但它并没有实现 Pointer 接口。但是,切片类型实现了该接口,因此如果你打印 &*mutant_zoo(或者 mutant_zoo.as_slice()),你将得到 vec 堆缓冲区的地址。mutant_zoo.as_ptr() 也可以工作,因为它返回 vec 缓冲区的原始指针。 - Masklinn
1个回答

7
如果你在一些POSIX系统上,你可以使用sbrk()系统调用,参数为0,以确定程序断点的当前位置,即堆的当前限制。如果给定值的地址小于该地址但大于堆的起始地址,则它位于堆中。我不知道如何检查它是否位于栈上,因为它并不是自动地不在堆上的替代品,因为它也可以是静态初始化或未初始化的数据,尽管这可能在检查代码时对您来说是显然的。在x86_64架构上,您可能可以使用rbp寄存器,它应该指向当前堆栈帧的开头,如果您想检查它是否位于当前堆栈帧上,或者如果您想检查它是否在任何堆栈上,您可以可能使用rsp
我认为你可以使用end()系统调用,使用end参数获取堆的起始位置。 因此,堆的下界将是end(end)的结果,上界将是sbrk(0)

1
大多数现代POSIX系统(/分配器)使用mmap获取堆空间,而不是(s)brk。在macOS上,(s)brk已正式弃用(会触发警告),在大多数BSD上也是如此(它们被称为"历史奇迹")。我认为正确的方法是获取堆栈基址(pthread_attr_getstackaddr),当前堆栈指针,并检查您的指针是否在两者之间。 - Masklinn

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接