为啥固定大小的缓冲区(数组)一定是不安全的?

Posted

技术标签:

【中文标题】为啥固定大小的缓冲区(数组)一定是不安全的?【英文标题】:Why is a fixed size buffers (arrays) must be unsafe?为什么固定大小的缓冲区(数组)一定是不安全的? 【发布时间】:2011-02-22 07:27:40 【问题描述】:

假设我想要一个 7 字节(或 3 或 777)的值类型。

我可以这样定义:

public struct Buffer71

    public byte b0;
    public byte b1;
    public byte b2;
    public byte b3;
    public byte b4;
    public byte b5;
    public byte b6;

定义它的更简单方法是使用固定缓冲区

public struct Buffer72

    public unsafe fixed byte bs[7];

当然,第二个定义更简单。问题在于必须为固定缓冲区提供的 unsafe 关键字。我知道这是使用指针实现的,因此不安全。

我的问题是为什么它一定是不安全的?为什么 C# 不能提供任意恒定长度的数组并将它们保留为值类型,而不是使其成为 C# 引用类型数组或不安全的缓冲区?

【问题讨论】:

【参考方案1】:

因为“固定缓冲区”不是真正的数组。它是一种自定义值类型,是我所知道的用 C# 语言生成一个值类型的唯一方法。 CLR 无法验证数组的索引是否以安全的方式完成。该代码也无法验证。最生动的演示:

using System;

class Program 
    static unsafe void Main(string[] args) 
        var buf = new Buffer72();
        Console.WriteLine(buf.bs[8]);
        Console.ReadLine();
    

public struct Buffer72 
    public unsafe fixed byte bs[7];

您可以在此示例中任意访问堆栈帧。恶意代码可以使用标准的缓冲区溢出注入技术来修补函数返回地址并强制您的代码跳转到任意位置。

是的,这很不安全。

【讨论】:

那么问题仅仅是 CIL 缺乏执行有界索引操作的任何方法吗?我看不出 CIL 不能提供这样一个特性的任何语义原因。诸如图形转换之类的某些事情可能比结构的“理想” 16 字节大小略高,但它们在逻辑上应该具有可变值语义。不可变语义使得在实例中调整值变得很痛苦,而可变引用语义引入了歧义,例如何时返回实例的函数将返回一个新实例或现有实例。 没有安全方法可以将固定大小的数组嵌入到结构中,这太疯狂了。对于代码的高性能部分,我几乎只想使用 100% blittable 结构。至少我们现在有 ref return 和 ref locals。 @JBeurer 我完全同意。他们选择以不安全的方式实现这一点似乎很愚蠢。存储和访问元素 FixedInts[8] 和字段 FixedInt8 之间基本上没有区别,并且由于大小是固定,编译器和 CLR 肯定可以访问他们需要的所有信息以使其安全,如果 .NET 团队决定正确使用它。我也很震惊我们无法创建固定的结构缓冲区。有时,不将所有东西都散布在堆上比理论设计理想更重要。我宁愿不要强迫我使用 C++。 -1 因为没有尝试回答问题。这个问题已经允许当前的实现是不安全的,所以这个答案没有任何价值。它询问为什么必须以不安全的方式实施它,而答案没有涉及到这一点。事实上,这个问题甚至问为什么 C# 不能提供一个实际的数组,而这个答案基本上是说,“因为他们不提供一个数组”。 Hmya,我经常被微软员工指责为设计疏忽。不要向信使开枪,把你的枪瞄准别处,不要告诉我这件事。它不是一个真正的数组,没有像样的方法可以在运行时发现固定大小的缓冲区的大小。没有 Length 属性,最多只能通过反射来实现索引检查。这太慢了。

以上是关于为啥固定大小的缓冲区(数组)一定是不安全的?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 OpenCV Mat 中保留固定的缓冲区大小?

指针为啥是不安全的呢?

无法将固定大小的字节数组从结构复制到 C# 结构中的另一个数组

POST请求responseType“数组缓冲区”是不是可行?

对于写入固定大小数组的不同部分的并行线程,是不是存在线程安全的 Java 数据结构?

如何得知socket的缓存大小,这个缓存是不是有