程序问答   发布时间:2022-06-02  发布网站:大佬教程  code.js-code.com
大佬教程收集整理的这篇文章主要介绍了为什么零长度的 stackalloc 会让 C# 编译器乐于允许有条件的 stackallocs?大佬教程大佬觉得挺不错的,现在分享给大家,也给大家做个参考。

如何解决为什么零长度的 stackalloc 会让 C# 编译器乐于允许有条件的 stackallocs??

开发过程中遇到为什么零长度的 stackalloc 会让 C# 编译器乐于允许有条件的 stackallocs?的问题如何解决?下面主要结合日常开发的经验,给出你关于为什么零长度的 stackalloc 会让 C# 编译器乐于允许有条件的 stackallocs?的解决方法建议,希望对你解决为什么零长度的 stackalloc 会让 C# 编译器乐于允许有条件的 stackallocs?有所启发或帮助;

下面的“修复”让我很困惑;这里的场景是根据大小有条件地决定是使用堆栈还是租用缓冲区 - 这是一个非常利基但有时必要的优化,但是:使用“明显”的实现(数字 3,推迟明确的分配,直到我们真正想要分配它),编译器用 CS8353 抱怨:

'Span@H_944_5@' 类型的 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

我在这里唯一能想到的是编译器真的标记 stackalloc 正在逃避 stackalloc 发生的上下文,并且正在挥动一个标志说“我无法证明这在后面的方法中是否安全”,但是通过在开始时使用 stackalloc[0],我们将“危险”上下文范围推得更高,现在编译器很高兴它永远不会逃脱“危险”范围(即它从未真正离开方法,因为我们在顶级范围内声明)。这种理解是否正确,就可以证明的内容而言,这只是编译器的限制?

真正(对我而言)有趣的是,= stackalloc[0] 基本上是一个无操作反正,这意味着至少在编译形成工作编号 1 = stackalloc[0] 与失败编号 2 = default 相同。

完整再现(也available on SharpLab to look at the 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,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 特性本质上是一系列关于给定值可以按值或按引用转义到哪个范围的规则。然这是根据方法范围编写的,但将其简化为以下两个语句之一会很有帮助:

  1. 不能从方法返回值@H_450_53@
  2. 可以从方法返回值@H_450_53@

span safety doc 详细介绍了如何为各种语句和表达式计算范围。不过,这里的相关部分是关于如何处理 locals。

主要的收获是本地是否可以返回是在本地声明时间计算的。在声明局部变量时,编译器会检查初始化器并决定局部变量是否可以从方法中返回。在有初始化器的情况下,如果能够返回初始化表达式,则本地将能够返回。

你如何处理声明了local但没有初始化器的情况?编译器必须做出决定:它可以还是不可以返回?在设计功能时,我们决定默认为“它可以被退回”,因为这是对现有模式造成最少摩擦的决定。

这确实给我们留下了一个问题,即开发人员如何声明一个不能安全返回但也缺少初始化程序的本地。最终我们确定了 = stackalloc [0] 的模式。这是一个可以安全优化的表达式,也是一个强有力的指标,基本上是一个要求,本地不安全返回。

知道这可以解释您所看到的行为:

  • Span<int> s = stackalloc[0]:返回是不安全的,因此后面的 stackalloc 成功@H_450_53@
  • Span<int> s = default:这是可以安全返回的,因为 default 可以安全返回。这意味着后面的 stackalloc 失败,因为您分配的值不能安全返回到标记为安全返回的本地@H_450_53@
  • Span<int> s;:这是可以安全返回的,因为这是未初始化的本地变量的默认值。这意味着后面的 stackalloc 失败,因为您分配的值不能安全返回到标记为安全返回的本地@H_450_53@

= stackalloc[0] 方法的真正缺点是它仅适用于 Span<T>。这不是 ref struct 的通用解决方案。在实践中,尽管对于其他类型来说这不是什么大问题。有一些关于我们如何使它more general 的猜测,但目前没有足够的证据证明这样做是合理的。

,

不是“为什么”的答案;但是,您可以将其更改为将数组赋值的结果切片为 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);
        }
    }
}

大佬总结

以上是大佬教程为你收集整理的为什么零长度的 stackalloc 会让 C# 编译器乐于允许有条件的 stackallocs?全部内容,希望文章能够帮你解决为什么零长度的 stackalloc 会让 C# 编译器乐于允许有条件的 stackallocs?所遇到的程序开发问题。

如果觉得大佬教程网站内容还不错,欢迎将大佬教程推荐给程序员好友。

本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
如您有任何意见或建议可联系处理。小编QQ:384754419,请注明来意。