gpt4 book ai didi

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

转载 作者:行者123 更新时间:2023-12-04 04:26:58 31 4
gpt4 key购买 nike

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


简短的再现(完整的再现如下)是:
// 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, 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特性本质上是一系列关于给定值可以通过值或引用转义到哪个范围的规则。虽然这是根据方法范围编写的,但简化为以下两个语句之一是有帮助的:

  • 该值不能从方法
  • 返回
  • 该值可以从方法
  • 中返回。

    span safety doc详细介绍了如何为各种语句和表达式计算范围。这里的相关部分是关于如何 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 的通用解决方案.在实践中,尽管对于其他类型来说这不是什么大问题。关于我们如何做到这一点有一些猜测 more general但目前还没有足够的证据证明这样做是合理的。

    关于c# - 为什么零长度的 stackalloc 会让 C# 编译器乐于允许有条件的 stackallocs?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/67289057/

    31 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com