如果stackalloc
与参考类型一起使用,如下所示
var arr = stackalloc string[100];
出现错误
不能获取地址,获取大小,或声明指向a的指针 托管类型('字符串')
为什么会这样?为什么CLR
无法声明指向托管类型的指针?
答案 0 :(得分:7)
"问题"更大:在C#中你不能指向托管类型。如果你尝试写(在C#中):
string *pstr;
你会得到:
无法获取地址,获取大小或声明指向托管类型的指针('字符串')
现在,stackalloc T[num]
会返回T*
(请参阅例如here),因此stackalloc
无法与引用类型一起使用。
你不能拥有指向引用类型的指针的原因可能与GC可以自由地在存储器周围移动引用类型(以压缩存储器)这一事实有关,因此指针的有效性可能是短。
请注意,在C ++ / CLI中,可以固定引用类型并获取其地址(请参阅pin_ptr)
答案 1 :(得分:2)
.NET中的Just-In-Time编译器在将C#编译器生成的MSIL转换为可执行机器代码时执行两项重要任务。明显且可见的是生成机器代码。不明显且完全不可见的工作是生成一个表,告诉垃圾收集器在方法执行时GC发生时查找对象引用的位置。
这是必要的,因为对象根不能仅作为类的字段存储在GC堆中,而是也存储在本地变量或CPU寄存器中。要正确完成这项工作,抖动需要知道堆栈帧的确切结构以及存储在那里的变量类型,以便正确创建该表。因此,稍后,垃圾收集器可以弄清楚如何读取正确的堆栈帧偏移量或CPU寄存器以获取对象根值。指向GC堆的指针。
使用stackalloc
时出现问题。该语法利用CLR功能,该功能允许程序声明自定义值类型。围绕普通托管类型声明的后门,具有此值类型不能包含任何字段的限制。只是一块内存,由程序生成适当的偏移到该blob。 C#编译器可以根据类型声明和索引表达式帮助您生成这些偏移量。
在C ++ / CLI程序中也很常见,相同的自定义值类型功能可以为本机C ++对象提供存储。只需要存储该对象的空间,如何正确初始化它并访问该C ++对象的成员是C ++编译器所发现的工作。 GC无需了解任何内容。
因此核心限制是无法为此blob内存提供类型信息。就CLR而言,这些只是没有结构的普通字节,GC使用的表没有选项来描述其内部结构。
不可避免地,您可以使用的唯一类型是不需要GC需要知道的对象引用的类型。 Blittable值类型或指针。所以System.String是不行的,它是一个引用类型。你可能得到的最接近的是“stringy”:
char** mem = stackalloc char*[100];
进一步的限制是,完全取决于您确保char *元素指向固定或非托管字符串。而且你没有将“数组”索引越界。这不太实际。
答案 2 :(得分:0)
因为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函数仍指向此无效的内存地址,这是无效状态。
如果通过引用传递本地基元,则会出现同样的问题。
架构是:
静态对象 - >生活在整个应用期间
本地对象 - >只要创建它们的范围有效,就会生存
堆分配的对象(使用new
创建) - >只要有人引用它们就存在。
另一个问题是垃圾收集在句点中起作用。当一个对象是本地的时,它应该在函数结束后立即完成,因为在那个时间之后 - 内存将被其他变量覆盖。 无论如何,GC都不能强制完成对象,或者不应该。
但好处是,C#JIT有时(并不总是)可以确定可以安全地在堆栈上分配对象,并且如果可能的话(有时候),将采用堆栈分配。
另一方面,在C ++中,您可以将所有内容声明为enywhere,但这比C#或Java的安全性更低,但您可以对应用程序进行微调并实现高性能 - 低资源应用程序
答案 3 :(得分:-1)
我认为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不能与引用类型一起使用的原因并不是为了防止这种错误。