【问题标题】:Span<T> does not require local variable assignment. Is that a feature?Span<T> 不需要局部变量赋值。这是一个特点吗?
【发布时间】:2018-09-14 03:24:38
【问题描述】:

我注意到即使局部变量未初始化,以下内容也会编译和执行。这是 Span 的一个特性吗?

void Uninitialized()
{
  Span<char> s1;
  var l1 = s1.Length;

  Span<char> s2;
  UninitializedOut(out s2);
  var l2 = s2.Length;
}

void UninitializedOut(out Span<char> s)
{}

【问题讨论】:

  • 对我来说似乎是一个错误,但据我所知,out 无论如何都不是由 CLR 强制执行的。
  • Span 并不特殊;您自己的struct Foo { public int Length =&gt; 0; } 也会触发此错误(或“次优处理”)。
  • @JeroenMostert 不过,不是一个错误; Foo确实没有字段,所以就明确赋值而言:没问题
  • @MarcGravell:是的——也不是。这是一件坏事:添加私有字段会破坏毫无戒心的代码,并且客户端无法控制的那种脆弱性是不可取的。允许这样做是用编译器有色眼镜看东西。它应该抱怨并强制new Foo()(当然,仍然编译成同样的东西),即使你可以按照规则解释为什么它不会。
  • @JeroenMostert 将公共或私有字段添加到结构将做到这一点 today;向结构添加字段不是安全的更改。考虑:Position pos; pos.X = 12; pos.Y = 42; Draw(pos);。这是用公共字段初始化可变结构的一种完全合法的方法(我现在将忽略带有公共字段的可变结构是否是它们自己好主意的问题.. .)。现在添加Z,您现有的代码将停止编译。

标签: c# c#-7.2 system.memory


【解决方案1】:

这看起来像是由引用程序集引起的问题,因为Span&lt;T&gt; 具有特定于框架的内部结构,所以这是必需的。

这意味着在引用程序集中:没有字段(编辑:这不太正确 - 见脚注)。

如果所有字段都已分配,则认为struct 已分配(出于“明确分配”的目的),在这种情况下,编译器会看到“已分配所有零个零字段:一切都很好 - 这个变量是分配”。但是编译器似乎并不知道 actual 字段,因此它被误导为允许在技术上无效的内容。

您绝对不应该依赖这种行为良好!尽管在大多数情况下.locals init 应该意味着您实际上并没有得到任何可怕的东西。但是,目前正在进行一些工作,以允许人们在某些情况下压制.locals init - 我不敢想会发生什么 这里的场景 - 特别是因为 Span&lt;T&gt; 的工作方式很像 ref T - 如果字段真的没有初始化为零,可能会变得非常非常危险。 p>

有趣的是,它可能已经修复:请参阅this example on sharplab。或者,sharplab 可能正在使用具体的目标框架,而不是参考程序集。


编辑:很奇怪,如果我将参考程序集加载到ildasm 或反射器中,我可以看到:

.field private initonly object _dummy

这是参考程序集中的欺骗字段,旨在阻止这种情况的发生,但是......现在看起来它的工作不太可靠!


更新:显然这里的区别是 subtle but known compiler issue 出于兼容性原因而保留;结构的明确分配考虑本地已知类型的私有字段,但不考虑外部程序集中类型的私有reference-type字段。

【讨论】:

  • 比这更微妙的是:它不考虑不同程序集中的引用类型的私有字段,但确实考虑了值类型的私有字段!
【解决方案2】:

马克有一个很好的答案。我想详细说明一下历史/背景。

首先,这绝对是compiler bug。根据明确分配的规则,这个本地不是明确分配的,任何使用都应该是错误的。不幸的是,由于多种原因,这个错误很难修复:

  • 这个错误很老,至少可以追溯到 C# 4.0。这让客户在 7 年多的时间里不经意间依赖它
  • BCL 中有许多具有这种基本结构的结构。例如CancellationToken

这些加在一起意味着修复此问题可能会破坏大量现有代码。尽管如此,C# 团队试图在 C# 6.0 中修复该错误时,该错误还很年轻。但是尝试使用此修复程序编译 Visual Studio 源代码表明,客户对依赖此错误的担忧是有根据的:存在许多构建中断。足以说服我们它会对大量代码产生负面影响。因此修复被撤销。

这里的第二个问题是所有编译器团队成员都不知道这个错误(至少在今天之前)。自从修复被撤消以来已经有大约 3 年了,并且从那时起有一点翻身。验证我们如何为Span&lt;T&gt; 生成参考程序集的团队成员并未意识到此错误,并根据语言规范推荐了当前设计。我是其中的开发者之一 :(

仍在讨论这个问题,但很可能我们会更新Span&lt;T&gt; 和其他类型的引用汇编策略,以避免这个编译器错误。

感谢您报告此事。很抱歉造成的混乱:(

【讨论】:

  • 这很好,谢谢。我理解改变编译器行为的复杂性甚至修复一个已知的错误,但是......在这种情况下,我觉得可以为至少提出一个论据,使其成为编译器警告。我知道即使这样(添加警告)也有问题,而且您可能已经在内部进行了大约 8 次相同的讨论。不过,这个问题很有趣!
  • 可能是一个幼稚的问题:在参考程序集中在private readonly object _dummy; 旁边添加private readonly int _evenDummier; 有什么害处,如果这会导致编译器发现它?尽管我猜这会导致与更改编译器以尊重 ref 类型私有字段完全相同的硬中断。也许在 new 参考程序集(如 System.Memory)中?
  • 在使用&lt;Features&gt;strict&lt;/Features&gt;时,这个故意的错误是否也有效?
【解决方案3】:

这或多或少是设计使然,因为这在很大程度上取决于底层 struct 本身是否包含任何字段。

此代码编译例如:

public struct MySpan<T>
{
    public int Length => 1;
}

static class Program
{
    static void Main(string[] args)
    {
        MySpan<char> s1;
        var l1 = s1.Length;
    }
}

但是这段代码没有:

public struct MySpan<T>
{
    public int Length { get; }
}

static class Program
{
    static void Main(string[] args)
    {
        MySpan<char> s1;
        var l1 = s1.Length;
    }
}

似乎在这种情况下,结构是默认的,这就是它不会抱怨缺少分配的原因。正如 Marc 的回答中所解释的那样,它没有检测到任何字段 一个错误。

【讨论】:

  • 我强烈怀疑这是由使字段不可见的引用程序集引起的错误
  • 这也不能解释为什么编译器不抱怨out参数。
  • 因为在这两种情况下都使用默认值初始化@DavidG
  • @DavidG 在我的回答中看到“零字段的全零”
  • 我猜 Marc 对原因有更清楚的说明,我的回答可以检测到根本原因。
猜你喜欢
  • 2018-09-14
  • 2022-01-07
  • 2020-08-22
  • 1970-01-01
  • 2016-07-13
  • 2018-06-02
  • 1970-01-01
  • 2019-08-19
  • 1970-01-01
相关资源
最近更新 更多