免责声明:这个结果比预期的要长
为什么 CLR 不支持大数组
CLR 不支持托管堆上的大型数组的原因有很多。
其中一些是技术性的,其中一些可能是“范式”。
这篇博客文章探讨了为什么存在限制的一些原因。本质上,由于内存碎片,决定限制(大写 O)对象的最大大小。实现处理较大对象的成本与存在需要如此大对象的用例并不多这一事实相权衡,而那些需要如此大对象的用例——在大多数情况下——是由于程序员的设计谬误造成的。因为对于 CLR,一切都是对象,所以这个限制也适用于数组。为了强制执行此限制,数组索引器设计为有符号整数。
但是一旦你确定你的程序设计需要你有这么大的数组,你就需要一个解决方法。
上面提到的博客文章还演示了您可以在不进入非托管领域的情况下实现大数组。
但正如 Evk 在评论中指出的那样,您希望通过 PInvoke 将整个数组传递给外部函数。这意味着您将需要非托管堆上的数组,否则必须在调用期间对其进行编组。对于这么大的数组,编组整个事情是一个坏主意。
解决方法
因此,由于托管堆是不可能的,您需要在非托管堆上分配空间并将该空间用于您的阵列。
假设您需要 8 GB 的空间:
long size = (1L << 33);
IntPtr basePointer = System.Runtime.InteropServices.Marshal.AllocHGlobal((IntPtr)size);
伟大的!现在您在虚拟内存中有一个区域,您可以在其中存储高达 8 GB 的数据。
我如何把它变成一个数组?
那么C#中有两种方法
“不安全”的方法
这将让您使用指针。并且可以将指针转换为数组。(在香草 C 中,它们通常是相同的)
如果您对如何通过指针实现 2D 数组有一个好主意,那么这将是您的最佳选择。
这是一个指针
“元帅”方法
您不需要不安全的上下文,而是必须将数据从托管堆“编组”到非托管堆。您仍然必须了解指针运算。
您要使用的两个主要功能是PtrToStructure和反向StructureToPtr。使用一个,您将从非托管堆上的指定位置获得值类型(例如双精度)的副本。使用另一个,您将在非托管堆上放置一个值类型的副本。
从某种意义上说,这两种方法都是“不安全的”。你需要知道你的指针
常见的陷阱包括但不限于:
- 忘记严格检查界限
- 混合我的元素的大小
- 弄乱对齐方式
- 混合你想要什么样的二维数组
- 忘记使用 2D 数组进行填充
- 忘记释放内存
- 忘记释放内存并使用它
您可能希望将 2D 阵列设计转变为 1D 阵列设计
在任何情况下,您都希望使用适当的检查和析构函数将其全部包装到一个类中。
灵感的基本例子
接下来是一个基于非托管堆的“类似”数组的泛型类。
特点包括:
- 它有一个接受 64 位整数的索引访问器。
- 它限制了
T
可以变成值类型的类型。
- 它有边界检查并且是一次性的。
如果你注意到了,我不做任何类型检查,所以如果Marshal.SizeOf
不能返回正确的数字,我们就掉进了上面提到的坑之一。
您必须自己实现的功能包括:
- 2D 访问器和 2D 数组算术(取决于其他库的期望,通常类似于
p = x * size + y
- 用于 PInvoke 目的的公开指针(或内部调用)
因此,如果有的话,仅将其用作灵感。
using static System.Runtime.InteropServices.Marshal;
public class LongArray<T> : IDisposable where T : struct {
private IntPtr _head;
private Int64 _capacity;
private UInt64 _bytes;
private Int32 _elementSize;
public LongArray(long capacity) {
if(_capacity < 0) throw new ArgumentException("The capacity can not be negative");
_elementSize = SizeOf(default(T));
_capacity = capacity;
_bytes = (ulong)capacity * (ulong)_elementSize;
_head = AllocHGlobal((IntPtr)_bytes);
}
public T this[long index] {
get {
IntPtr p = _getAddress(index);
T val = (T)System.Runtime.InteropServices.Marshal.PtrToStructure(p, typeof(T));
return val;
}
set {
IntPtr p = _getAddress(index);
StructureToPtr<T>(value, p, true);
}
}
protected bool disposed = false;
public void Dispose() {
if(!disposed) {
FreeHGlobal((IntPtr)_head);
disposed = true;
}
}
protected IntPtr _getAddress(long index) {
if(disposed) throw new ObjectDisposedException("Can't access the array once it has been disposed!");
if(index < 0) throw new IndexOutOfRangeException("Negative indices are not allowed");
if(!(index < _capacity)) throw new IndexOutOfRangeException("Index is out of bounds of this array");
return (IntPtr)((ulong)_head + (ulong)index * (ulong)(_elementSize));
}
}