不同的行为取决于缓冲区的大小

时间:2013-10-19 22:44:02

标签: c# .net clr

我有一个简单的值类型,用于包装在堆栈上分配的缓冲区

unsafe struct Value
{
    // public fixed byte Data[1073741800]; // StackOverflow (2^30 - 24)
    // public fixed byte Data[1073741801]; // InvalidProgram
    // public fixed byte Data[2147483631]; // InvalidProgram
    // public fixed byte Data[2147483632]; //OutOfMemory (2^31 - 16)
    public fixed byte Data[2147483647]; // OutOfMemory
}

以这种方式使用:

unsafe static void Main(string[] args)
{
    Value value;
    value.Data[0] = 0;
}

如果缓冲区太大,则可能发生堆栈溢出,这是预期的。

但是根据缓冲区的大小,CLR的行为会有所不同:

  • 尺寸高达1073741800(2 ^ 30 - 24)会抛出 StackOverflowException

  • 对于[1073741801,2147483631] 范围内的,会抛出 InvalidProgramException

  • 对于范围[2147483632(2 ^ 31 - 16),2147483647] ,它会抛出 OutOfMemoryException

如果重要的话,那就是发布x86版本。

它在运行时间方面是一致的:与.Net 2.0和.Net 4.5相同的行为,所以看起来它不是一个bug。

为什么会出现这种不同的行为?

是否记录在某处?

感谢任何指针。


修改

我已经使用 Mono 3.2.3 进行了测试,行为类似,即存在三个错误级别:SO - > IP - > OOM,但门槛不一样。

我不知道Mono团队现在如何开发它的实现,如果它完全独立于它是否从MS实现中“复制粘贴”。

在第一种情况下,这意味着必须在某处指定此行为,因为两个不同的团队独立工作会产生相同的奇怪行为,这太巧合了,所以他们必须遵循相同的规范。

在第二种情况下,他们可能只是在MS实现中导入了一个小故障......

可以从任何团队获得一些反馈。

如果这里没有反馈,我会发一些MS和Mono的门票。

1 个答案:

答案 0 :(得分:0)

投机

我认为这与该过程中的哪一步失败有关。

第一步是分配对象。 32位.NET应用程序不能引用超过2 ^ 31个字节的内存, 我相信 ,而您的应用程序的其余部分使用其他16个字节。我猜你为什么不在这里得到堆栈溢出异常是因为它实际上还没有分配内存,只是检查它是否可能分配内存。或者,根据CLR如何实现堆栈与堆的关系,可能是堆栈是虚拟概念,分配实际上是在此步骤完成,然后虚拟堆栈将指向它。

下一步是尝试JIT编译类型I的构造函数, 相信 。此时,JIT编译将失败,因为无论出于何种原因,它无法引用大于2 ^ 30 - 24的数组元素,为什么它不能,我不确定。我在ECMA-355中找不到任何相关信息。我不确定这是编译器错误还是CLR错误。假设规范没有指定数组大小限制,那么我将其称为CLR错误。如果是这样,那么它就是一个编译器错误,用于生成无法进行JIT编译的代码。

最后,它尝试将事物放在堆栈上并且失败,正如您所期望的那样,因为对象可能比堆栈大小大得多。如步骤1中所述,可能在内部CLR堆栈是虚拟概念而不是堆栈大小的物理内存块。如果是这种情况,那么此处发生的所有事情都是为您在堆上分配的对象创建指针,并使用+= size_of_thing_being_referenced更新堆栈大小。或者,如果堆栈是CLR中的实际内存块,则在此步骤中尝试堆栈分配并失败。

ECMA-355§III.1简要介绍了堆栈管理是如何实现细节的,这意味着CLR能够使其发挥作用,并且您已经找到了一种方法来瞥见这种魔法。