将静态数组转换为Delphi中的指针?

ala*_*ncc 5 arrays delphi

最近,在使用Delphi数组时,我遇到了一些问题,这些问题使我对其进行了更仔细的考虑。

我写了一个测试函数:

procedure TForm1.Button2Click(Sender: TObject);
var
  MyArray1: array[0..0] of UInt64;
  MyArray2: array of UInt64;
  Value1, Value2: UInt64;
begin
  SetLength(MyArray2, 1);

  MyArray1[0] := 100;
  MyArray2[0] := 100;

  Value1 := PUInt64(@MyArray1)^;
  Value2 := PUInt64(@MyArray2)^;

  Value1 := PUInt64(@MyArray1[0])^;
  Value2 := PUInt64(@MyArray2[0])^;

  //Value1 := PUInt64(MyArray1)^;
  Value2 := PUInt64(MyArray2)^;
end;
Run Code Online (Sandbox Code Playgroud)

以我的理解,静态数组存储第一个元素,第二个元素等的值。而动态数组存储数组的地址。

因此,PUInt64(@ MyArray2)^实际上将包含64位计算机中阵列的地址,而其中一半包含32位计算机中的地址。

但是,为什么PUInt64(MyArray1)^是无效的强制转换?

而且似乎PUInt64(@ MyArray2 [0])^是最安全的转换,因为它可同时用于静态和动态数组。那是对的吗?

And*_*and 7

首先,让我首先赞扬您编写了一个如此精心研究的问题!

基本上,您似乎大部分都正确。在Delphi中,静态数组是一种值类型,例如单个整数或整数记录。使用sizeof任何这样的变量,你得到的数据的完整大小。另一方面,动态数组是引用类型。该变量仅存储指向实际数组数据的单个指针。因此sizeof,在动态数组上仅产生指针的大小。

当然,这也会影响分配的结果:如果使用复制静态数组a := b,则会复制所有数据,最后得到两个独立的数组。如果复制动态数组,则仅复制指针,最后以指向同一数据的两个指针结束。

因此,回到您的代码:

Value1 := PUInt64(@MyArray1)^;
Run Code Online (Sandbox Code Playgroud)

是的,因为MyArray1“是”的数据,与开始100@MyArray1是一个指针100的值。类型转换是正确的(因为数组中的数据类型为UInt64,指向该值的指针的类型为PUInt64),并且通过解引用,您得到100

Value2 := PUInt64(@MyArray2)^;
Run Code Online (Sandbox Code Playgroud)

是的,因为MyArray2是指向实际数据的指针,所以是指向实际数据@MyArray2的指针的指针。在64位进程中,指针是64位整数,因此类型转换有效。通过取消引用,可以将原始指针恢复为实际数据。但是在32位进程中执行此操作是一个错误。

更简单地说,您可以写(*)

Value2 := NativeUInt(MyArray2);
Run Code Online (Sandbox Code Playgroud)

让我们继续

Value1 := PUInt64(@MyArray1[0])^;
Run Code Online (Sandbox Code Playgroud)

这很容易:MyArray1[0]是your 100,然后您获取地址,然后取消引用指针以返回原始值。

Value2 := PUInt64(@MyArray2[0])^;
Run Code Online (Sandbox Code Playgroud)

此处完全相同。

Value2 := PUInt64(MyArray2)^;
Run Code Online (Sandbox Code Playgroud)

基本上是我上面提到的情况(在*处),只是取消引用了。在32位和64位应用程序中均有效。(PUInt64由于的原因,P它具有本机大小;它可能是32位。)实际上,MyArray2 是指向an的指针UInt64,即a PUInt64,因此通过取消引用它,就可以得到该UInt64值(100)。

然而,

Value1 := PUInt64(MyArray1)^;
Run Code Online (Sandbox Code Playgroud)

是一个错误。MyArray1就您而言,与完全相同UInt64。这不是指向这样的事情的指针-它不是一个PUInt64。当然,您可以向编译器撒谎,并告诉它:“嘿,将此视为指向的指针UInt64”。但是通过取消引用它,您尝试获取UInt64at地址100,这将导致访问冲突,因为您不拥有内存中的该区域(很可能)。

由于大小匹配,因此在64位处理中,代码将进行编译。PUInt64具有指针的大小(64位),并且MyArray1在声明中具有64位的大小。

在32位进程中,由于大小不匹配,因此代码将无法编译。PUInt64具有指针的大小(32位),但是MyArray1在声明中具有64位的大小。

而且似乎PUInt64(@ MyArray2 [0])^是最安全的转换,因为它可同时用于静态和动态数组。那是对的吗?

通常,我觉得静态数组和动态数组是两个非常不同的东西,因此我不会尝试在两种情况下都强制代码看起来相同。

您是否要获取数组中的第一个值?如果是这样,只需写MyArray2[0]。(或者MyArray1[0]在静态数组的情况下。)您是否在尝试获取第一个元素的地址?如果是这样,我更喜欢NativeUInt(MyArray2)代替NativeUInt(@MyArray2[0])(或pointerPUInt64)。实际上,如果数组为空,则第一种方法会产生0nil,而另一种则是错误。

更新:

为了弄清楚,如果a是静态数组,则第一个元素的地址就是@a(或@a[0])。如果b为动态数组,则第一个元素的地址为pointer(b)(或@b[0])。您可以将任何指针转换为其他一些指针大小的类型,例如NativeUInt(整数类型)或PUInt64(特定指针类型)或PCardinal(特定指针类型)或...。那不会改变实际值,只会改变其编译时的解释。