【问题标题】:c# What happen if span<T>(memory<T>) refers to buffers on stackc# 如果 span<T>(memory<T>) 引用堆栈上的缓冲区会发生什么
【发布时间】:2018-12-02 04:28:52
【问题描述】:

.NET Standard 2.1 引入了一项新功能,您可以在其中“控制”内存块而不是复制它们:SpanMemory

在文档示例中,我注意到可以引用堆栈缓冲区:

byte data = 0;
Span<byte> stackSpan = stackalloc byte[100];
for (int ctr = 0; ctr < stackSpan.Length; ctr++)
   stackSpan[ctr] = data++;

据我所知,进程的堆栈内存是有限的(1MB 或 4MB),我们无法手动释放它。

所以我猜想创建一个Memory&lt;T&gt;Span&lt;T&gt; 会以某种方式“固定”堆栈上的内存位置,以便我们可以索引它?但这不是堆栈泄漏的潜在情况,可能导致堆栈溢出吗? 由于堆栈上的数组应该与Memory&lt;T&gt;Span&lt;T&gt; 一样长。

【问题讨论】:

  • 是的,如果你在栈上分配太多内存会导致栈溢出,但是在栈上分配数组的机制(stackalloc)并不是新的。它从一开始就在 C# 中,并且需要使用 unsafe 关键字。除此之外,还有其他更常见的方法会导致堆栈溢出。
  • @MikeZboray 也许我没有描述清楚。我的意思是“堆栈泄漏”
  • 没有。创建指向 stackalloc 内存的指针始终有效,不需要固定(堆栈不会移动)并且不会增加分配大小。您唯一需要担心的是不使用超出分配生命周期的跨度。方法结束。没问题,跨度也无法幸免。
  • @HansPassant 但是 Memory 怎么样,它可以是一个类的字段。为了能够通过索引访问数据。只要 Memory 对象存在,堆栈上的数据就应该存在。就像示例一样,它应该是有效的,直到 GC 杀死 Memory 实例。我说的对吗?
  • 什么是“堆栈泄漏”?您似乎认为对堆栈分配数组的引用可能会以某种方式逃逸到堆中。这不是我对功能的理解。 Span&lt;T&gt; 可以引用堆栈或堆上的底层内存,但它必须位于堆栈上。 Memory&lt;T&gt; 可以引用仅在堆上的内存。它本身是一个普通的结构,所以它不需要分配,但不像Span&lt;T&gt; 那样绑定到堆栈。 Memory&lt;T&gt; 类似于 ArraySegment&lt;T&gt;。见Stephen Toub's article

标签: c# memory stack-overflow .net-standard


【解决方案1】:

这是安全的,因为 Span 的生命周期将与堆栈分配的数组相同或更短。

您不能至少直接将stackalloc 的结果分配给Memory&lt;T&gt;(我认为即使是不安全的代码也无济于事-C#: convert generic pointer to array),因此将其范围限定为Span&lt;T&gt;

根据您发布的link,Span 生命周期与其定义的范围相关:

Span&lt;T&gt; 是一个 ref 结构,分配在堆栈上而不是托管堆上。 Ref struct 类型有许多限制,以确保它们不能被提升到托管堆,

请注意,ref struct 禁止某些操作,包括您应该关注的操作 - 从分配堆栈的方法返回 Span&lt;T&gt;。由于结果 span 将同时(或更早)被销毁,然后堆栈帧包含由 stackalloc 创建的数组。

   static Span<byte> MySpan()
   {
        Span<byte> span = stackalloc byte[100];
        // error CS8352: Cannot use local 'span' in this context because it may 
        // expose referenced variables outside of their declaration scope
        return span;
    }

MSDN 杂志文章(2018 年 1 月)C# - All About Span: Exploring a New .NET Mainstay Stephen Toub 也对此进行了介绍。

【讨论】:

  • 嗯...但是如果作为参数发送,您可以返回 Span 吗?看起来编译器对 Span 做了一些特殊处理
  • @joe 返回方法的参数不会延长这些参数的生命周期,因为调用者方法必须在堆栈上(并因此保留分配的数据)直到同步方法返回(您不能将 Span 用作异步正是出于这个原因,方法的参数——它“释放”了调用方法的堆栈)。结果,当您尝试在方法中返回 Span created 时,您会看到不同的行为。
  • @joe 我的理解是“特殊处理”是针对更广泛的ref struct 而不是直接针对Span&lt;T&gt;,但根据规范肯定会添加大量检查。
猜你喜欢
  • 2022-01-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-03-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-12-24
相关资源
最近更新 更多