Ifstackalloc
与以下引用类型一起使用
var arr = stackalloc string[100];
有错误
无法获取托管类型(“字符串”)的地址、大小或声明指向托管类型的指针
为什么会这样?为什么CLR
不能声明指向托管类型的指针?
Ifstackalloc
与以下引用类型一起使用
var arr = stackalloc string[100];
有错误
无法获取托管类型(“字符串”)的地址、大小或声明指向托管类型的指针
为什么会这样?为什么CLR
不能声明指向托管类型的指针?
将 C# 编译器生成的 MSIL 转换为可执行机器代码时,.NET 中的即时编译器执行两项重要任务。显而易见的是生成机器代码。不明显且完全不可见的工作是生成一个表,告诉垃圾收集器在方法执行期间发生 GC 时在哪里查找对象引用。
这是必要的,因为对象根不能仅仅作为类的字段存储在 GC 堆中,还可以存储在局部变量或 CPU 寄存器中。为了正确地完成这项工作,jitter 需要知道堆栈帧的确切结构和存储在那里的变量的类型,以便它可以正确地创建该表。这样,稍后,垃圾收集器可以弄清楚如何读取正确的堆栈帧偏移或 CPU 寄存器来获取对象根值。指向 GC 堆的指针。
这是你使用时的问题stackalloc
。该语法利用了允许程序声明自定义值类型的 CLR 功能。围绕正常托管类型声明的后门,限制此值类型不能包含任何字段。只是一个内存块,由程序来为该块生成适当的偏移量。C# 编译器可帮助您根据类型声明和索引表达式生成这些偏移量。
在 C++/CLI 程序中也很常见,相同的自定义值类型特性可以为原生 C++ 对象提供存储。只需要存储该对象的空间,如何正确初始化它并访问该 C++ 对象的成员是 C++ 编译器的一项工作。GC 不需要知道什么。
所以核心限制是没有办法为这个内存块提供类型信息。就 CLR 而言,这些只是没有结构的纯字节,GC 使用的表没有描述其内部结构的选项。
不可避免地,您可以使用的唯一类型是不需要 GC 需要知道的对象引用的类型。Blittable 值类型或指针。所以 System.String 是不行的,它是一个引用类型。您可能得到的最接近“粘稠”的是:
char** mem = stackalloc char*[100];
进一步的限制是,确保 char* 元素指向固定或非托管字符串完全取决于您。并且您不会索引“数组”超出范围。这不是很实用。
因为与 C++ 相比,C# 致力于内存安全的垃圾收集,所以您应该了解内存管理的细微差别。
例如,看看下面的代码:
public static void doAsync(){
var arr = stackalloc string[100];
arr[0] = "hi";
System.Threading.ThreadPool.QueueUserWorkItem(()=>{
Thread.Sleep(10000);
Console.Write(arr[0]);
});
}
该程序很容易崩溃。因为arr
是堆栈分配的,对象+它的内存一结束就会消失doAsync
。lamda 函数仍然指向这个不再有效的内存地址,这是无效状态。
如果你通过引用传递本地原语,也会发生同样的问题。
架构是:
静态对象 -> 存在于整个应用程序时间
本地对象 -> 只要创建它们的Scope是有效
的堆分配对象(使用创建new
) -> 只要有人持有对它们的引用,就存在。
另一个问题是垃圾收集在周期中起作用。当一个对象是本地的时,它应该在函数结束后立即完成,因为在那之后 - 内存将被其他变量覆盖。无论如何,不能强制 GC 或不应该强制完成对象。
好消息是,C# JIT 有时(并非总是)可以确定可以在堆栈上安全地分配对象,并且如果可能(再次,有时)将诉诸堆栈分配。
另一方面,在 C++ 中,您可以在任何地方声明所有内容,但这比 C# 或 Java 的安全性要低,但您可以微调您的应用程序并实现高性能 - 低资源应用程序
我认为 Xanatos 发布了正确的答案。
无论如何,这不是一个答案,而是另一个答案的反例。
考虑以下代码:
using System;
using System.Threading;
namespace Demo
{
class Program
{
static void Main(string[] args)
{
doAsync();
Thread.Sleep(2000);
Console.WriteLine("Did we finish?"); // Likely this is never displayed.
}
public static unsafe void doAsync()
{
int n = 10000;
int* arr = stackalloc int[n];
ThreadPool.QueueUserWorkItem(x => {
Thread.Sleep(1000);
for (int i = 0; i < n; ++i)
arr[i] = 0;
});
}
}
}
如果您运行该代码,它将崩溃,因为堆栈数组被写入之后,堆栈内存已被释放。
这表明 stackalloc 不能与引用类型一起使用的原因不仅仅是为了防止这种错误。