【问题标题】:How could a for loop be related to a stack overflow?for 循环如何与堆栈溢出有关?
【发布时间】:2019-11-14 01:38:12
【问题描述】:

我遇到过一段代码,只有当 for 循环重复一定次数时才会导致堆栈溢出异常。

代码如下:

public class Stackoverflow
{
    public static void Test()
    {
        List<int> list = new List<int>() {1, 2};

        Container container = new Container {List = list};
        for (int i = 0; i < 10000; i++)     // This matters
        {
            foreach (var item in container.List)
            {
                Console.WriteLine(item);
            }
        }
    }
}

public class Container
{
    private IEnumerable<int> list;

    public IEnumerable<int> List
    {
        get
        {
            //return list.OrderBy(x => x);  <- This is OK
            list = list.OrderBy(x => x);    // This is not
            return list;
        }
        set { list = value; }
    }

}

当 Test() 方法执行时,you can see a long series of "1" and "2" being printed on screen before the error actually happens.(我认为这意味着 for 循环正在正常推进)

如果条件变为“i

The memory dump showed "List.Orderby" is likely the direct cause of the problem

我知道写这样的“get”是不好的做法,但我不明白是什么导致了这里的堆栈溢出异常,以及为什么它似乎是累积的而不是递归的死亡陷阱。

这可能是枚举代码中的一个陷阱,或者是编译器的什么,或者只是我的疏忽? 无论如何,正在寻找解释,谢谢您的帮助。

Stacktrace here

在文本中:

000000e86215e460 00007ffe28670f7c (MethodDesc 00007ffe28567738 +0x2c System.Linq.OrderedEnumerable`1[[System.Int32, mscorlib]].GetEnumerator())
000000e86215e4a0 00007ffe849ce396 (MethodDesc 00007ffe844a4ce8 +0x66 System.Linq.Buffer`1[[System.Int32, mscorlib]]..ctor(System.Collections.Generic.IEnumerable`1<Int32>))
000000e86215e4c0 00007ffe86bad279 (MethodDesc 00007ffe866d7630 +0x19 System.StubHelpers.StubHelpers.SafeHandleRelease(System.Runtime.InteropServices.SafeHandle))
000000e86215e510 00007ffe28670fe7 (MethodDesc 00007ffe28567e80 +0x47 System.Linq.OrderedEnumerable`1+<GetEnumerator>d__1[[System.Int32, mscorlib]].MoveNext())
000000e86215e520 00007ffe28670f7c (MethodDesc 00007ffe28567738 +0x2c System.Linq.OrderedEnumerable`1[[System.Int32, mscorlib]].GetEnumerator())
000000e86215e560 00007ffe849ce396 (MethodDesc 00007ffe844a4ce8 +0x66 System.Linq.Buffer`1[[System.Int32, mscorlib]]..ctor(System.Collections.Generic.IEnumerable`1<Int32>))
000000e86215e580 00007ffe86c5b552 (MethodDesc 00007ffe867e3f60 +0xf2 DomainNeutralILStubClass.IL_STUB_PInvoke(Microsoft.Win32.SafeHandles.SafeFileHandle, Byte*, Int32, Int32 ByRef, IntPtr))
000000e86215e5d0 00007ffe28670fe7 (MethodDesc 00007ffe28567e80 +0x47 System.Linq.OrderedEnumerable`1+<GetEnumerator>d__1[[System.Int32, mscorlib]].MoveNext())
000000e86215e5e0 00007ffe28670f7c (MethodDesc 00007ffe28567738 +0x2c System.Linq.OrderedEnumerable`1[[System.Int32, mscorlib]].GetEnumerator())
000000e86215e5e8 00007ffe86c5b526 (MethodDesc 00007ffe867e3f60 +0xc6 DomainNeutralILStubClass.IL_STUB_PInvoke(Microsoft.Win32.SafeHandles.SafeFileHandle, Byte*, Int32, Int32 ByRef, IntPtr))
000000e86215e620 00007ffe849ce396 (MethodDesc 00007ffe844a4ce8 +0x66 System.Linq.Buffer`1[[System.Int32, mscorlib]]..ctor(System.Collections.Generic.IEnumerable`1<Int32>))
000000e86215e690 00007ffe28670fe7 (MethodDesc 00007ffe28567e80 +0x47 System.Linq.OrderedEnumerable`1+<GetEnumerator>d__1[[System.Int32, mscorlib]].MoveNext())
000000e86215e6a0 00007ffe28670f7c (MethodDesc 00007ffe28567738 +0x2c System.Linq.OrderedEnumerable`1[[System.Int32, mscorlib]].GetEnumerator())
000000e86215e6e0 00007ffe849ce396 (MethodDesc 00007ffe844a4ce8 +0x66 System.Linq.Buffer`1[[System.Int32, mscorlib]]..ctor(System.Collections.Generic.IEnumerable`1<Int32>))
000000e86215e710 00007ffe86b9b46a (MethodDesc 00007ffe86950f90 +0x8a System.IO.StreamWriter.Flush(Boolean, Boolean))
000000e86215e750 00007ffe28670fe7 (MethodDesc 00007ffe28567e80 +0x47 System.Linq.OrderedEnumerable`1+<GetEnumerator>d__1[[System.Int32, mscorlib]].MoveNext())
000000e86215e760 00007ffe28670f7c (MethodDesc 00007ffe28567738 +0x2c System.Linq.OrderedEnumerable`1[[System.Int32, mscorlib]].GetEnumerator())
000000e86215e7a0 00007ffe849ce396 (MethodDesc 00007ffe844a4ce8 +0x66 System.Linq.Buffer`1[[System.Int32, mscorlib]]..ctor(System.Collections.Generic.IEnumerable`1<Int32>))
000000e86215e7d0 00007ffe28670da5 (MethodDesc 00007ffe28565c68 +0xe5 ConsoleTest.Container.get_List())
000000e86215e810 00007ffe28670fe7 (MethodDesc 00007ffe28567e80 +0x47 System.Linq.OrderedEnumerable`1+<GetEnumerator>d__1[[System.Int32, mscorlib]].MoveNext())
000000e86215e820 00007ffe28670f7c (MethodDesc 00007ffe28567738 +0x2c System.Linq.OrderedEnumerable`1[[System.Int32, mscorlib]].GetEnumerator())
000000e86215e860 00007ffe2867065f (MethodDesc 00007ffe28565b98 +0x11f ConsoleTest.Stackoverflow.Test())
000000e86215e900 00007ffe286704ba (MethodDesc 00007ffe28565ac0 +0x3a ConsoleTest.Program.Main(System.String[]))

【问题讨论】:

  • 我们需要堆栈跟踪。
  • list = list.OrderBy(x =&gt; x) 构建了一个更大的表达式树。经过几次迭代后,您将拥有 &lt;originalList&gt;.OrderBy(x =&gt; x).OrderBy(x =&gt; x).OrderBy(x =&gt; x)... 这与 return list.OrderBy(x =&gt; x) 完全不同。
  • 我不完全理解你的问题,但这似乎是基于对 LINQ 实际工作方式的误解。 LINQ 查询使用延迟执行;这是众所周知的,很容易搜索。 .OrderBy() 充当转换/构建器;在枚举整个查询之前实际上什么都不会发生(这对于 LINQ 允许您为外部提供程序(如数据库)构建查询的方式至关重要)。没有 eval() 的意思是没有文本被解析为代码,这里 - 而是 LINQ 方法充当构建器。
  • 阅读下面彼得斯的回答,它应该告诉你发生了什么。 IEnumerable&lt;T&gt; 是延迟评估的东西,如果您将 LINQ 表达式存储到这样的变量中,您将等待执行该表达式,直到您对其进行迭代,因为您将延迟评估的表达式堆叠在延迟评估的表达式之上,最终堆栈倒塌。
  • 简短:这:IEnumerable&lt;int&gt; x = someList.OrderBy(i =&gt; i); 实际上并没有对列表进行排序。无论列表有多大,这段代码实际上都非常便宜。 但是,您存储的是如何获取值的“配方”,稍后在您迭代此 x 变量时使用,当您这样做时,首先我们将对列表进行排序,然后我们将返回有序值。如果您甚至订购x,您现在有 2 个订购步骤。

标签: c# stack-overflow


【解决方案1】:

问题在于,您的 get 属性每次都会用 list.OrderBy(x =&gt; x); 定义的新对象替换 list 变量。

请注意,OrderBy(x =&gt; x) 还没有进行任何排序,它创建了一个定义如何返回项目的对象,该对象仅在您使用例如迭代它时才会执行。 foreach.

我将尝试展示这样做的后果:

  • 在第一次运行之前,list 等于 List&lt;int&gt;() {1, 2}
  • 运行一次后,list 等于 (List&lt;int&gt;() {1, 2}).OrderBy(x =&gt; x)
  • 运行两次后,list 等于 (List&lt;int&gt;() {1, 2}).OrderBy(x =&gt; x).OrderBy(x =&gt; x)
  • 在运行 10000 次之后...我什至不会尝试显示 list 包含的内容。

枚举该 10000 倍嵌套对象会导致 10000 个 Enumerator 对象被旋转,每个对象都对前一个对象执行有序检索,直到遇到原始源 List&lt;int&gt;。显然它在到达源列表之前空间不足。

解决方法是不要每次都重新分配列表,或者(如果您觉得出于某种原因必须这样做)然后使用 ToList() 使其“执行”OrderBy:

get
{
    list = list.OrderBy(x => x).ToList(); // creates a new list and does not keep to OrderBy() object
    return list;
}

每次访问该属性时,这仍会导致多余的重新排序和重新分配。如果您总是需要排序,将逻辑放在 setter 中会更有效率,所以它只会发生一次:

get { return list; }
set { list = value.OrderBy(x => x).ToList(); }

【讨论】:

  • 可以说,更好的解决方案是更改set 以合并OrderBy 并保留get 作为值的简单返回。这不需要在必要时实现列表,但没有嵌套的灾难(以及更改 getter 中的值,这只是糟糕的时期)。原来直接返回list.OrderBy(...)的做法当然也可以。
  • 谢谢!我现在终于明白了。
  • @JeroenMostert 是的!该逻辑属于 setter。
猜你喜欢
  • 2017-12-15
  • 1970-01-01
  • 2014-12-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-10-26
  • 2019-05-18
相关资源
最近更新 更多