假设我想要一个 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];
}
当然第二个定义更简单。问题在于必须为固定缓冲区提供不安全的关键字。我知道这是使用指针实现的,因此不安全。
我的问题是为什么它一定是不安全的?为什么 C# 不能提供任意恒定长度数组并将其保留为值类型,而不是使其成为 C# 引用类型数组或不安全的缓冲区?
因为“固定缓冲区”不是真正的数组。它是一种自定义值类型,大约是我所知道的用 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];
}
您可以任意访问本例中的堆栈帧。恶意代码可利用标准缓冲区溢出注入技术来修补函数返回地址并强制您的代码跳转到任意位置。
是的,这很不安全。
作为现在发现此内容的人的注释。
readonly
修饰符结合使用,这允许您(几乎)保证对可变引用类型(或可变 struct
类型)(包括数组)的不可变引用。
public struct Test
{
public readonly int[] fixedSizeArray = new int [5];
/// <summary>I'm important, don't forget me.</summary>
public Test() {}
}
唯一的警告是:
readonly
字段一样,我相信这可以在构造函数中根据需要进行修改(只要您小心不要在构造函数中重新分配它,这就不应该是太大的问题,因为我相信无法派生结构)。
int i;
将是 0
,任何引用类型(如数组)将是 null
),忽略显式初始化程序(出于某种原因,我不太确定)。话虽这么说,声明公共无参数构造函数(也在 C#10 中引入)并将其留空应该会产生所需的行为(至少如果我正确阅读 docs 和 featureproposal)。因此,为了使我们之前的示例正常工作,它必须具有(看似)无用的无参数构造函数。这行不通:// THIS WON'T WORK
public struct Test
{
public readonly int[] fixedSizeArray = new int[5];
}
default
运算符 将始终跳过任何和所有构造函数,因此理论上人们可以创建没有正确大小的结构实例。话虽如此,该字段仍然是 readonly
,所以我很确定没有人能够拉出一个快速字段并错误地使用它,因为他们会被 null
数组卡住。话虽如此,作为一种边缘情况,它仍然值得注意。对于缓冲区,您可以将其与 Memory 结合起来并非常接近。如果您不想使用不安全的代码,但想要一个 C++ 式的缓冲区,那么它可能是您的最佳选择。
从 C# 12 开始,您可以将内联数组声明为结构类型:
[System.Runtime.CompilerServices.InlineArray(10)]
public struct CharBuffer
{
private char _firstElement;
}