为什么零长度 stackalloc 会让 C# 编译器乐意允许条件 stackalloc?

2024-05-01

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

类型为“Span”的 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

我在这里唯一能想到的是编译器是really标记为stackalloc正在逃避其中的上下文stackalloc发生了,并且挥舞着旗帜说“我无法证明这在该方法的后面是否是安全的”,但是通过让stackalloc[0]一开始,我们将“危险”上下文作用域推得更高,现在编译器很高兴它永远不会逃脱“危险”作用域(即它永远不会真正离开该方法,因为我们在顶部作用域声明) 。这种理解是否正确,这只是编译器在可证明方面的限制?

What's really(对我来说)有趣的是= stackalloc[0]基本上是一个空操作anyway, 意思是至少以编译的形式工作号码1= stackalloc[0]与失败的数字 2 相同= default.

完整重现(也可在 SharpLab 上查看 IL https://sharplab.io/#v2:EYLgxg9gTgpgtADwGwBYA0AXEBLANgHwAEAmARgFgAoQgBgAJDSA6AIQFcAzDmKAZwG4qVQgGYGpJA2J0AwlQDeVOsoZjGkwijoBlDAEMwAawCCuXBDAAxNgDsAFNhsY6kWxgCUSlYsoq/dAHoAugBiUjoMAAtsXjoYulwYAHM9XDQ6ACs2XmdHbAxsVOwALxhY2L0KugATGA49NlxnXgAHPRsvfx02mwAeRwwAPjpYgF4R/SNU8zA4pwBtGgBdQV8uuk7/INDpKPj9s2TUkDoeKGhZbQAOEQBWERPjOlheRucIDjo9CYNDaYtTggWi9eNgIB01l1th8IgBPFowOgAcm0PX6TkGSJc7RsEGcwER2Rg1TmEWisUgThgCHxMDADV4iPydAAtnpYZs/NsCYCWhBGSSIGwMKDanQYVFEZT9HkbElWTAohBqpyVNtUe10UMRnRxrV6m9VusNpCtsEQmJKnFYntYnZUrwIHFDik0nFak5sPTcKcoOcoJ5TVzghq+gNhgJVcoo3MMPMluKAG48UGlEnjGyNXBG9YYKAcoPeGN+bCfOyuJx0Xp0ETEQPGvzyNXBGTXO5iSJ6FoImwVZy2n5TMwWYvrMaDv7D2YDeYVjArUfKAC+i9OuEZq58DfWEGTfBKxN1dGMfvZAAUIBBcFrBkxtJ3YNUmAAlGBOctCpzuHPbvzjmwwAA7t0mrhnYu4pge1TpDQ6Rzt+q4roWXQXi0jR6BgMB2LwCHIf4AAiEDaBALKKtEcoAOr5JE2G4caSHGhwjjTAWxpbr+pZ0OBe6poe8S4s4mZmPWv7sb+KgnlA56Xte4Z3g+xIvoqbBQPYEH7mmdG/gx6w6cuQimsC2CJhhiLqAwWioehmF2KGN4jCJXSjMMUTnMBAHAQAcniACSLItIkpFUtUACiCBgDALQFOCdjfoEwSAZ2mF7psRkmZh4gaFohHEaReyUdRdivno1QAPI2LgsJ2eGDkxs5ZJuXQHl0N5GB+QFMBBZhoXhZF0X2HF2yJaZKWmps2wCTAJz3hAgHeaVLQABpfDYJIzXNEALQAmi4JEtHgTIegU3qVZk2RYONwS4NUMx6EwsSwXM+QQMAGQgWGGLpNdMxMDQmzmZoOiRLN81LbFmxif41UYjq4w5L8/zTgsyw/gRREkWRjhJFRUS0ajemZRZQMg5tLRbeDpqQ340PauO+oNE0qN+DlGP5djhU4fjVBLkAA=).

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);
    }
}

The Span<T> / ref特性本质上是一系列关于给定值可以通过值或引用转义到哪个范围的规则。虽然这是根据方法范围编写的,但将其简化为以下两个语句之一会很有帮助:

  1. 该值无法从方法返回
  2. 该值可以从方法中返回

The 跨度安全文档 https://github.com/dotnet/csharplang/blob/main/proposals/csharp-7.2/span-safety.md详细介绍了如何计算各种语句和表达式的范围。这里的相关部分是如何locals https://github.com/dotnet/csharplang/blob/main/proposals/csharp-7.2/span-safety.md#locals被处理。

主要的一点是,当地人是否可以返回是在当地申报时间计算的。在声明局部变量时,编译器会检查初始值设定项并决定局部变量是否可以从方法返回。在存在初始化器的情况下,如果能够返回初始化表达式,则本地将能够返回。

如何处理声明了本地变量但没有初始化程序的情况?编译器必须做出决定:是否可以返回?在设计该功能时,我们决定默认设置为“可以返回”,因为该决定对现有模式造成的摩擦最少。

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

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

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

真正的缺点是= stackalloc[0]方法是它只适用于Span<T>。这不是一个通用的解决方案ref struct。但实际上,对于其他类型来说这并不是什么大问题。关于我们如何做到这一点有一些猜测更一般 https://github.com/dotnet/csharplang/blob/main/proposals/low-level-struct-improvements.md#allowing-attributes-on-locals但目前还没有足够的证据证明这样做是合理的。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

为什么零长度 stackalloc 会让 C# 编译器乐意允许条件 stackalloc? 的相关文章

  • 如何使用 C# 中的参数将用户重定向到 paypal

    如果我有像下面这样的简单表格 我可以用它来将用户重定向到 PayPal 以完成付款
  • 我如何才能等待多个事情

    我正在使用 C 11 和 stl 线程编写一个线程安全队列 WaitAndPop 方法当前如下所示 我希望能够将一些内容传递给 WaitAndPop 来指示调用线程是否已被要求停止 如果 WaitAndPop 等待并返回队列的元素 则应返回
  • 通过 CMIS (dotCMIS) 连接到 SP2010:异常未经授权

    我正在使用 dotCMIS 并且想要简单连接到我的 SP2010 服务器 我尝试用 C 来做到这一点 如下所示http chemistry apache org dotnet getting started with dotcmis htm
  • Web 客户端和 Expect100Continue

    使用 WebClient C NET 时设置 Expect100Continue 的最佳方法是什么 我有下面的代码 我仍然在标题中看到 100 continue 愚蠢的 apache 仍然抱怨 505 错误 string url http
  • 按成员序列化

    我已经实现了template
  • 在结构中使用 typedef 枚举并避免类型混合警告

    我正在使用 C99 我的编译器是 IAR Embedded workbench 但我认为这个问题对于其他一些编译器也有效 我有一个 typedef 枚举 其中包含一些项目 并且我向该新类型的结构添加了一个元素 typedef enum fo
  • 关于 C++ 转换:参数 1 从“[some_class]”到“[some_class]&”没有已知的转换

    我正在研究 C 并且遇到了一个错误 我不知道确切的原因 我已经找到了解决方案 但仍然想知道原因 class Base public void something Base b int main Base b b something Base
  • 将 VSIX 功能添加到 C# 类库

    我有一个现有的单文件生成器 位于 C 类库中 如何将 VSIX 项目级功能添加到此项目 最终目标是编译我的类库项目并获得 VSIX 我实际上是在回答我自己的问题 这与Visual Studio 2017 中的单文件生成器更改 https s
  • C#中如何移动PictureBox?

    我已经使用此代码来移动图片框pictureBox MouseMove event pictureBox Location new System Drawing Point e Location 但是当我尝试执行时 图片框闪烁并且无法识别确切
  • 创建链表而不将节点声明为指针

    我已经在谷歌和一些教科书上搜索了很长一段时间 我似乎无法理解为什么在构建链表时 节点需要是指针 例如 如果我有一个节点定义为 typedef struct Node int value struct Node next Node 为什么为了
  • WCF 中 SOAP 消息的数字签名

    我在 4 0 中有一个 WCF 服务 我需要向 SOAP 响应添加数字签名 我不太确定实际上应该如何完成 我相信响应应该类似于下面的链接中显示的内容 https spaces internet2 edu display ISWG Signe
  • 显示UnityWebRequest的进度

    我正在尝试使用下载 assetbundle统一网络请求 https docs unity3d com ScriptReference Networking UnityWebRequest GetAssetBundle html并显示进度 根
  • while 循环中的 scanf

    在这段代码中 scanf只工作一次 我究竟做错了什么 include
  • 垃圾收集器是否在单独的进程中运行?

    垃圾收集器是否在单独的进程中启动 例如 如果我们尝试测量某段代码所花费的进程时间 并且在此期间垃圾收集器开始收集 它会在新进程上启动还是在同一进程中启动 它的工作原理如下吗 Code Process 1 gt Garbage Collect
  • 如何查看网络连接状态是否发生变化?

    我正在编写一个应用程序 用于检查计算机是否连接到某个特定网络 并为我们的用户带来一些魔力 该应用程序将在后台运行并执行检查是否用户请求 托盘中的菜单 我还希望应用程序能够自动检查用户是否从有线更改为无线 或者断开连接并连接到新网络 并执行魔
  • 这些作业之间是否存在顺序点?

    以下代码中的两个赋值之间是否存在序列点 f f x 1 1 x 2 不 没有 在这种情况下 标准确实是含糊不清的 如果你想确认这一点 gcc 有这个非常酷的选项 Wsequence point在这种情况下 它会警告您该操作可能未定义
  • 如何在Xamarin中删除ViewTreeObserver?

    假设我需要获取并设置视图的高度 在 Android 中 众所周知 只有在绘制视图之后才能获取视图高度 如果您使用 Java 有很多答案 最著名的方法之一如下 取自这个答案 https stackoverflow com a 24035591
  • 混合 ExecutionContext.SuppressFlow 和任务时 AsyncLocal.Value 出现意外值

    在应用程序中 由于 AsyncLocal 的错误 意外值 我遇到了奇怪的行为 尽管我抑制了执行上下文的流程 但 AsyncLocal Value 属性有时不会在新生成的任务的执行范围内重置 下面我创建了一个最小的可重现示例来演示该问题 pr
  • 是否可以在 .NET Core 中将 gRPC 与 HTTP/1.1 结合使用?

    我有两个网络服务 gRPC 客户端和 gRPC 服务器 服务器是用 NET Core编写的 然而 客户端是托管在 IIS 8 5 上的 NET Framework 4 7 2 Web 应用程序 所以它只支持HTTP 1 1 https le
  • 对来自流读取器的过滤数据执行小计

    编辑问题未得到解答 我有一个基于 1 个标准的过滤输出 前 3 个数字是 110 210 或 310 给出 3 个不同的组 从流阅读器控制台 问题已编辑 因为第一个答案是我给出的具体示例的字面解决方案 我使用的实际字符串长度为 450 个

随机推荐