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

8

最近在使用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;

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

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

但是为什么 PUInt64(MyArray1)^ 是无效转换?

同时,似乎 PUInt64(@MyArray2[0])^ 是最安全的转换方式,因为它适用于静态和动态数组。这正确吗?


使用 Value1 := PUInt64(MyArray1[0])^; 替代 Value1 := PUInt64(MyArray1)^; - Schneider Infosystems Ltd
2
@SchneiderInfosystemsLtd:这不是一个好主意。MyArray1[0]UInt64,而不是指向 UInt64 的指针(100 不是有效地址)。在 32 位进程中,大小甚至都不匹配(指针是 32 位的,但 MyArray1[0] 是 64 位的)。 - Andreas Rejbrand
@Andreas Rejjbrand:是的,你说得对,PUInt64(MyArray1[0])^等于PUInt64(100)^。这会导致在地址0x064(100)处发生读取访问冲突。 - Schneider Infosystems Ltd
1个回答

9
首先,我要夸奖一下您写出了这样一个经过深入研究的问题!基本上,您已经理解得很正确。在Delphi中,静态数组是值类型,就像单个整数或整数记录一样。使用sizeof操作符,可以获得数据的完整大小。另一方面,动态数组是引用类型。变量仅存储指向实际数组数据的单个指针。因此,在动态数组上使用sizeof操作符只会返回指针的大小。
当然,这也会影响赋值时发生的事情:如果您使用a := b操作符复制静态数组,则会复制所有数据,并且最终得到两个独立的数组。如果您复制动态数组,则只复制指针,并且最终得到两个指向相同数据的指针。
所以,回到您的代码:
Value1 := PUInt64(@MyArray1)^;

是的,因为MyArray1是以100开头的数据,所以@MyArray1是指向100值的指针。类型转换是正确的(因为数组中的数据类型是UInt64,指向该值的指针的类型是PUInt64),通过解引用,您可以得到100
Value2 := PUInt64(@MyArray2)^;

是的,因为MyArray2是实际数据的指针,所以@MyArray2是指向实际数据指针的指针。在64位进程中,指针是64位整数,所以类型转换是有效的。通过取消引用,您可以获得返回实际数据的原始指针。但在32位进程中这样做是错误的。

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

Value2 := NativeUInt(MyArray2);

让我们继续进行

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

这很简单: MyArray1 [0] 是你的 100,你获取地址然后取消引用指针以获得原始值。
Value2 := PUInt64(@MyArray2[0])^;

我的想法完全相同。

而且

Value2 := PUInt64(MyArray2)^;

这基本上就是我在上面提到的情况(在*处),只不过它被解除引用了。它在32位和64位应用程序中都是有效的。(由于P的存在,PUInt64的大小为本地大小,可能为32位)。实际上,MyArray2是指向UInt64的指针,也就是PUInt64,因此通过解除引用它,您可以获得那个UInt64值(100)。
然而,
Value1 := PUInt64(MyArray1)^;

这是一个错误。在您的情况下,MyArray1 实际上与 UInt64 完全相同。它不是指向这样一个东西的指针,也不是 PUInt64。当然,您可以欺骗编译器并告诉它:“嘿,把这个当作指向 UInt64 的指针来处理。” 但是通过对它进行解引用,您将尝试获取地址为 100UInt64 值,这将导致访问冲突,因为您并没有拥有该内存区域(很可能)。

在 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(特定指针类型)等。这不会改变实际值,只会改变编译时的解释。


非常感谢您的解释,让我更清楚地理解了这个问题。实际上,我犹豫是否要问这个问题,因为它不是一个完整的真实案例。我在真实案例中遇到了这个问题。然后我想进一步研究这个问题,而不仅仅是让我的代码运行。所以我写了上面的测试片段来验证我的观点。我需要做的就是将数组转换为指针,并传递给用C++编写的DLL。当使用@ MyArray1进行转换时,一切正常。但是对于@ MyArray2则不起作用,最后我使用PUInt64(@ MyArray2[0])进行测试。 - alancc

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接