2

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

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 的行为相同,所以看起来这不是一个错误。

为什么会有这种不同的行为?

它是否记录在某处?

感谢您的任何指示。


编辑

我用Mono 3.2.3进行了测试,行为相似,即存在三个级别的错误:SO -> IP -> OOM,但阈值不一样。

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

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

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

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

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

4

1 回答 1

0

推测

我相信这与过程中的哪一步失败有关。

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

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

最后,它尝试将事物放入堆栈,但正如您所料,它失败了,因为该对象可能远大于您的堆栈大小。正如第一步中提到的,CLR 堆栈在内部可能是一个虚拟概念,而不是堆栈大小的物理内存块。如果是这种情况,那么这里发生的所有事情就是创建一个指向您在堆上分配的对象的指针,并且堆栈大小更新为+= size_of_thing_being_referenced. 或者,如果堆栈是 CLR 中的实际内存块,则在此步骤尝试堆栈分配但失败。

ECMA-355 §III.1 简要介绍了堆栈管理是如何成为一个实现细节的,这意味着 CLR 会发挥神奇的作用来使其工作,并且您已经找到了一种了解这种神奇的方法。

于 2013-10-19T23:19:43.543 回答