免责声明:这个结果比预期的要长。
为什么CLR不支持大数组
CLR不支持托管堆上的大型数组有多种原因。
其中一些是技术性的,有些可能是“范例”。
这个博客帖子讨论了为什么有限制的一些原因。实际上,由于内存碎片,决定限制(大写O)对象的最大大小。实现处理较大对象的成本与这样一个事实进行了权衡,即不存在需要这么大对象的许多用例,而且在大多数情况下,这些都是由于程序员的设计谬误造成的。而且,对于CLR来说,所有东西都是一个对象,这个限制也适用于数组。为了执行这一限制,数组索引器被设计成有符号整数。
但是,一旦您确定,您的程序设计要求您有如此大的数组,您将需要一个解决办法。
上面提到的博客文章还演示了您可以实现大数组,而无需进入非托管领域。
但是,正如Evk在注释中指出的那样,您希望通过PInvoke将数组作为一个整体传递给外部函数。这意味着您需要非托管堆上的数组,或者在调用期间必须对其进行封送处理。用这么大的数组编组整件事情是个坏主意。
解决办法
因此,由于托管堆是不可能的,因此您需要在非托管堆上分配空间,并将该空间用于数组。
假设您需要8GB的空间:
代码语言:javascript运行复制long size = (1L << 33);
IntPtr basePointer = System.Runtime.InteropServices.Marshal.AllocHGlobal((IntPtr)size);太棒了!现在,在虚拟内存中有一个区域,您可以在其中存储高达8GB的数据。
如何将其转换为数组?
在C#中有两种方法
“不安全”办法
这将使您可以使用指针。指针可以转换为数组。(在香草C中,它们通常是一样的)
如果您对如何通过指针实现2D数组有一个很好的想法,那么这将是最好的选择。
这是一个指针
“元帅”方法
您不需要不安全的上下文,而是必须将数据从托管堆“封送”到非托管堆。你还得理解指针算法。
您需要使用的两个主要函数是PtrToStructure和反向StructureToPtr。有了一个,您将从非托管堆上的指定位置获得值类型(例如double)的副本。对于另一个,您将在非托管堆上放置一个值类型的副本。
从某种意义上说,这两种方法都是“不安全的”。你需要知道你的指针
常见的陷阱包括但不限于:
忘记严格检查边界混淆了我的元素的大小搞砸了对齐混淆你想要什么样的二维阵列忘了用二维数组填充忘记释放内存忘记释放内存,无论如何都要使用它您可能希望将2D数组设计转换为一维数组设计
在任何情况下,您都希望将其包装到一个具有适当的检查和引导符的类中。
灵感的基本例子
下面是一个基于非托管堆的“类似”数组的泛型类。
特征包括:
它有一个接受64位整数的索引访问器。它将T可以变为的类型限制为值类型。它有边界检查,是一次性的。如果您注意到,我不做任何类型检查,因此,如果Marshal.SizeOf未能返回正确的数字,我们正在跌入上述坑之一。
您必须自己实现的特性包括:
2D访问器和2D数组算法(取决于其他库所期望的内容,通常类似于p = x * size + y )用于PInvoke目的的公开指针(或内部调用)所以,如果有的话,只把它当作一种灵感。
代码语言:javascript运行复制using static System.Runtime.InteropServices.Marshal;
public class LongArray
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
}
}
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));
}
}