簡體   English   中英

為什么零長度堆棧分配使 C# 編譯器樂於允許條件堆棧分配?

[英]Why does a zero-length stackalloc make the C# compiler happy to allow conditional stackallocs?

下面的“修復”讓我很困惑; 這里的場景是根據大小有條件地決定是使用堆棧還是租用緩沖區 - 這是一個非常利基但有時是必要的優化,但是:使用“明顯”的實現(第 3 號,推遲明確的分配,直到我們真正想要分配它),編譯器向 CS8353 抱怨:

'Span<int>' 類型的 stackalloc 表達式的結果不能在此上下文中使用,因為它可能暴露在包含方法之外

簡短的復制(完整的復制如下)是:

// take your pick of:
// Span<int> s = stackalloc[0]; // works
// Span<int> s = default; // fails
// Span<int> s; // fails

if (condition)
{   // CS8353 happens here
    s = stackalloc int[size];
}
else
{
    s = // some other expression
}
// use s here

我在這里唯一能想到的是,編譯器確實在標記stackallocstackalloc發生 stackalloc 的上下文,並且揮舞着一個標志說“我無法證明這是否會在以后安全方法”,但是通過在開始時使用stackalloc[0] ,我們將“危險”上下文 scope 推得更高,現在編譯器很高興它永遠不會逃脫“危險” scope(即它實際上從未離開方法,因為我們在頂級范圍內聲明)。 這種理解是否正確,就可以證明的內容而言,這只是編譯器的限制?

(對我來說)真正有趣的是= stackalloc[0]基本上是無操作,這意味着至少在編譯形式中,工作編號 1 = stackalloc[0]與失敗的編號 2 = default相同。

完整再現(也可在 SharpLab 上查看 IL )。

using System;
using System.Buffers;

public static class C
{
    public static void StackAllocFun(int count)
    {
        // #1 this is legal, just initializes s as a default span
        Span<int> s = stackalloc int[0];
        
        // #2 this is illegal: error CS8353: A result of a stackalloc expression
        // of type 'Span<int>' cannot be used in this context because it may
        // be exposed outside of the containing method
        // Span<int> s = default;
        
        // #3 as is this (also illegal, identical error)
        // Span<int> s;
        
        int[] oversized = null;
        try
        {
            if (count < 32)
            {   // CS8353 happens at this stackalloc
                s = stackalloc int[count];
            }
            else
            {
                oversized = ArrayPool<int>.Shared.Rent(count);
                s = new Span<int>(oversized, 0, count);
            }
            Populate(s);
            DoSomethingWith(s);
        }
        finally
        {
            if (oversized is not null)
            {
                ArrayPool<int>.Shared.Return(oversized);
            }
        }
    }

    private static void Populate(Span<int> s)
        => throw new NotImplementedException(); // whatever
    private static void DoSomethingWith(ReadOnlySpan<int> s)
        => throw new NotImplementedException(); // whatever
    
    // note: ShowNoOpX and ShowNoOpY compile identically just:
    // ldloca.s 0, initobj Span<int>, ldloc.0
    static void ShowNoOpX()
    {
        Span<int> s = stackalloc int[0];
        DoSomethingWith(s);
    }
    static void ShowNoOpY()
    {
        Span<int> s = default;
        DoSomethingWith(s);
    }
}

Span<T> / ref功能本質上是一系列規則,scope 給定值可以通過值或引用轉義。 雖然這是根據方法范圍編寫的,但將其簡化為以下兩個語句之一會很有幫助:

  1. 該值不能從方法返回
  2. 該值可以從方法返回

跨度安全文檔詳細介紹了如何針對各種語句和表達式計算 scope。 不過,這里的相關部分是關於如何處理本地人的。

主要的收獲是,本地是否可以返回是在本地聲明時計算的。 在聲明局部變量時,編譯器檢查初始化器並決定局部變量是否可以從方法返回。 如果有初始化程序,那么如果能夠返回初始化表達式,則本地將能夠返回。

您如何處理聲明了本地但沒有初始化程序的情況? 編譯器必須做出決定:它可以返回還是不能返回? 在設計該功能時,我們決定默認為“它可以返回”,因為這是對現有模式造成最小摩擦的決定。

這確實給我們留下了一個問題,即開發人員如何聲明一個不能安全返回但又缺少初始化程序的本地。 最終我們確定了= stackalloc [0]的模式。 這是一個可以安全優化的表達式,也是一個強有力的指標,基本上是一個要求,即本地返回是不安全的。

知道這解釋了您所看到的行為:

  • Span<int> s = stackalloc[0] :返回是不安全的,因此后面的stackalloc成功
  • Span<int> s = default :這是可以安全返回的,因為default可以安全返回。 這意味着后面的stackalloc失敗,因為您正在分配一個不安全的值以返回到標記為可安全返回的本地
  • Span<int> s; :這是安全的返回,因為這是未初始化的本地人的默認值。 這意味着后面的stackalloc失敗,因為您正在分配一個不安全的值以返回到標記為可安全返回的本地

= stackalloc[0]方法的真正缺點是它僅適用於Span<T> 這不是ref struct的通用解決方案。 在實踐中,盡管對於其他類型來說這不是什么大問題。 有一些關於我們如何使它更普遍的猜測,但沒有足夠的證據證明在這一點上這樣做是合理的。

不是“為什么”的答案; 但是,您可以將其更改為三元運算符,將數組分配的結果切片到 Span:

public static void StackAllocFun(int count)
{
    int[] oversized = null;
    try
    {
        Span<int> s = ((uint)count < 32) ?
            stackalloc int[count] :
            (oversized = ArrayPool<int>.Shared.Rent(count)).AsSpan(0, count);

        Populate(s);
        DoSomethingWith(s);
    }
    finally
    {
        if (oversized is not null)
        {
            ArrayPool<int>.Shared.Return(oversized);
        }
    }
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM