为什么stackalloc不能与引用类型一起使用?

时间:2016-02-24 09:25:11

标签: c# stackalloc

如果stackalloc与参考类型一起使用,如下所示

var arr = stackalloc string[100];

出现错误

  

不能获取地址,获取大小,或声明指向a的指针   托管类型('字符串')

为什么会这样?为什么CLR无法声明指向托管类型的指针?

4 个答案:

答案 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不能与引用类型一起使用的原因并不是为了防止这种错误。