C#中的数组何时分配在堆栈上?

36

我一直在尝试弄清楚什么时候会在堆栈上分配内存,但我无法想象如何使数组(或其中的值)在堆栈上分配内存;

以这个例子为例:

public void foo()
{
    int bar[] = new int [10];
}

在堆上分配10个int结构体,只有指向它们的指针才会在栈上,是吗?

如何使固定大小的数组放在栈上?如果我正在使用我定义的结构体怎么办?

如果我想要将数组大小作为参数传递给函数呢?

据我所知,当函数被调用时,只要在调用时已知大小,就应该没有问题可以在栈上获取任意大小的数组。

是否应该为此而烦恼?据我所知,将此固定大小数组放在栈上会提高性能,因为不会进行堆分配。


4
我应该为此感到烦恼吗?可能不需要。听起来你正在过早地优化和以太低的层次思考。 - mason
stackalloc 可用于强制堆栈分配一定数量的字节。它有其用途,但您应该非常确定您需要它 - 大多数程序(特别是业务应用程序)不需要它。 - xxbbcc
堆栈分配的数组是所有恶意软件攻击中非常大比例的罪魁祸首。 - Hans Passant
2个回答

34

10个int结构将在堆上被分配,只有指向它们的指针会在栈上,对吗?

是的,正确。

如何使固定大小数组进入堆栈?如果我正在使用我定义的结构体会怎样?

stackalloc关键字可以实现此目的。但是,它仅在不安全的上下文中起作用,在大多数情况下这是一个不必要的限制因素,并且不值得性能的折衷。

例如:

public void unsafe foo()
{
    int* bar = stackalloc int [10];
}

你将需要使用指针算术来访问数组的成员。

如果我想要将数组大小作为参数传递给函数怎么办?只要在调用函数时知道大小,就应该没有问题在堆栈上获取任意大小的数组。

按预期工作:

public void unsafe foo(int length)
{
    int* bar = stackalloc int [length];
}

我应该被这个困扰吗?据我了解,在堆栈上获取此固定大小数组将改善性能,因为无需执行堆分配。

一般来说,不需要。除非你处理一些非常特定的性能关键场景,比如重型数学计算、加密、压缩等,否则它并没有带来真正的好处。

此外,请参考此问题以了解与性能相关的讨论。


1
谢谢,我喜欢在Stack Overflow上看到人们对一些琐碎问题给出非常详细的解释。 - morowinder
除了stackalloc之外,您还可以在堆栈上分配固定大小的缓冲区。例如: unsafe struct foo { public fixed int bar[10]; } - Gladclef
17
Span<T>类型的引入意味着不再需要使用不安全指针。public void foo() { Span<int> bar = stackalloc int[10]; } - EddPorter
在编译时大小未知的情况下,如何可能在堆栈上分配数组? - Algo
3
@algo:这是一个问答网站,你说的听起来像是一个问题。但在发出问题之前,建议您仔细思考一下。按照定义,分配直到程序运行时才进行。编译器缺少某些知识为什么会对运行时的行为有影响呢?在提问前,先澄清你的问题。 - Eric Lippert

10

在评论中提到了这一点,但没有人费心更新任何答案,所以我发表了一个。C#现在有一种特殊的结构类型,非常适合这个目的:

public void foo()
{
    Span<int> bar = stackalloc int [10];
}

摘自文档:

Span是一个ref结构体,在堆栈上分配而不是在托管堆上。 Ref结构类型有许多限制,以确保它们不能被提升到托管堆,包括它们不能被装箱,不能被分配给Object、dynamic或任何接口类型的变量,不能是引用类型中的字段,并且不能跨await和yield边界使用。此外,对两个方法Equals(Object)和GetHashCode的调用会引发NotSupportedException异常。

Span<T> Struct


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