【问题标题】:StackOverflow exception when initializing array初始化数组时出现 StackOverflow 异常
【发布时间】:2019-06-25 12:08:47
【问题描述】:

我调用该方法并得到 StackOverflowException。它不是递归调用,它只包含数组初始化。我需要一个 BigIntegers 数组,该代码适用于 int 数组,即使大小更大。我展示了简化的示例,在实际代码中我不能使用循环来填充数组,因为我无法生成我需要的数字,所以我必须对它们全部进行硬编码。

设置: x64 模式,.Net Core

error details我们可以看出:

1) 堆栈跟踪为空

2) 错误可能源自 System.Collections.ListDictionaryInternal

    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Before"); // <--- This is displayed

            var a = GetBigIntegers(); // <--- Method is called

            Console.WriteLine("After"); // <--- We will never get there
        }


        static BigInteger[] GetBigIntegers()
        {
            // <--- Crash here
            return new BigInteger[]
            {
                1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
                // Many more lines (850-900) and they are 2-3 times longer than here
                1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
            };
        }
    }

我检查了 IL 代码,它看起来是正确的,并且需要将近 400 000 行。

.method private hidebysig static 
    valuetype [System.Runtime.Numerics]System.Numerics.BigInteger[] GetBigIntegers () cil managed 
{
    // Method begins at RVA 0x207c
    // Code size 1130123 (0x113e8b)
    .maxstack 4
    .locals init (
        [0] valuetype [System.Runtime.Numerics]System.Numerics.BigInteger[]
    )

    // (no C# code)
    IL_0000: nop
    IL_0001: ldc.i4 66500
    IL_0006: newarr [System.Runtime.Numerics]System.Numerics.BigInteger
    IL_000b: dup
    IL_000c: ldc.i4.0
    //  return new BigInteger[66500]IL_000d: ldc.i4.1
    IL_000e: call valuetype [System.Runtime.Numerics]System.Numerics.BigInteger [System.Runtime.Numerics]System.Numerics.BigInteger::op_Implicit(int32)
    // (no C# code)
    IL_0013: stelem [System.Runtime.Numerics]System.Numerics.BigInteger
    IL_0018: dup
    IL_0019: ldc.i4.1
    IL_001a: ldc.i4.1
    IL_001b: call valuetype [System.Runtime.Numerics]System.Numerics.BigInteger [System.Runtime.Numerics]System.Numerics.BigInteger::op_Implicit(int32)
    IL_0020: stelem [System.Runtime.Numerics]System.Numerics.BigInteger
.....
    IL_113e75: dup
    IL_113e76: ldc.i4 66499
    IL_113e7b: ldc.i4.1
    IL_113e7c: call valuetype [System.Runtime.Numerics]System.Numerics.BigInteger [System.Runtime.Numerics]System.Numerics.BigInteger::op_Implicit(int32)
    IL_113e81: stelem [System.Runtime.Numerics]System.Numerics.BigInteger
    IL_113e86: stloc.0
    IL_113e87: br.s IL_113e89

    IL_113e89: ldloc.0
    IL_113e8a: ret
} // end of method Program::GetBigIntegers

我希望数组会被初始化并返回,但实际上我得到了 StackOverflow 错误。

我知道我可以使用不同的方法来做同样的事情,但我想知道为什么它不能以这种方式工作。希望每个阅读这个问题的人也能感兴趣。

【问题讨论】:

  • 也发布堆栈跟踪怎么样?
  • 删除其中几行会发生什么?删除这 900 行中的 90%,看看错误是否仍然发生。
  • 我猜你超过了 40 亿大关?
  • 检查您是在 x86 还是 x64 模式下运行项目。

标签: c# .net-core runtime-error runtime runtimeexception


【解决方案1】:

实际原因是评估堆栈帧大小不足以容纳所有推入的内容。

原因隐藏在 JIT 编译器优化的背后,这些优化不是针对大方法内部的结构初始化执行的(这会导致生成性能不佳的机器代码)。

Source.

【讨论】:

  • 我已阅读您的参考资料,这看起来像是我的问题。但我不明白 JIT 编译器将这些对象放入堆栈的位置。从 IL 我看到任何时候堆栈上的项目都不超过 4 个。引用数组 (1)、引用数组 (2)、索引 (3)、要存储的值 (4)。您能否解释一下为什么我看不到导致溢出的分配?
  • 对,这是对 jit 如何处理调用返回的结构的限制。大型结构通过引用隐式返回。所以在内部,当 jit 看到一个返回大型结构的方法时,它会分配一个“jit 临时”来处理返回值。如果你有一个像这样的大型初始化器列表,那么每个初始化器都会变成一个调用,并且每个调用都会获得它自己的 jit 临时结构。 jit 在重用这个空间方面并不聪明,所以每个临时结构在堆栈上都有自己的槽。并且 jit 还可以对其他临时对象进行额外的复制。
  • 该方法需要在序言中为所有这些临时对象分配空间。所以在方法的开始会有一个非常大的栈指针调整,这个调整会触发栈溢出。
  • 检测何时可以重用临时对象以避免像这样炸毁堆栈并不像人们希望的那样简单。但这是我们应该解决的问题。正如github.com/dotnet/coreclr/issues/14103 所说,解决方法是将初始化程序声明为 int[] 数组,然后循环填充 BigInteger 数组。
猜你喜欢
  • 2019-05-13
  • 1970-01-01
  • 1970-01-01
  • 2023-03-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多