Recently when using Delphi arrays, I encounter some problems which make me consider them more carefully.
I write a test function:
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;
In my understanding, the static array stores the value of the first element, second element, and so on. while the dynamic array store the address of the array.
Therefore, PUInt64(@MyArray2)^ will actually contain the address of the array in 64bit computer, and half of it contain the address in 32bit computer.
But why PUInt64(MyArray1)^ is an invalid cast?
Also it seems PUInt64(@MyArray2[0])^ is the safest cast since it works on both static and dynamic arrays. Is that correct?
First let me start by complimenting you for writing such a well-researched question!
Basically, it seems like you got most of it right. In Delphi, a static array is a value type, like a single integer or a record of integers. Using sizeof on any such variable, you get the full size of the data. On the other hand, a dynamic array is a reference type. The variable stores only a single pointer to the actual array data. So sizeof on a dynamic array yields only the size of the pointer.
Of course, this also affects what happens on assignments: if you copy a static array using a := b, you copy all the data, and end up with two independent arrays. If you copy a dynamic array, you only copy the pointer, and end up with two pointers to the same data.
So, back to your code:
Value1 := PUInt64(@MyArray1)^;
Yes, since MyArray1 "is" the data that starts with 100, @MyArray1 is a pointer to the 100 value. The typecast is correct (since the data type in the array is UInt64, the type of a pointer to that value is PUInt64), and by dereferencing, you get 100.
Value2 := PUInt64(@MyArray2)^;
Yes, since MyArray2 is a pointer to the actual data, @MyArray2 is a pointer to the pointer to the actual data. In a 64-bit process, pointers are 64-bit integers, so the typecast is valid. By dereferencing, you get back the original pointer to the actual data. But it is a bug to do this in a 32-bit process.
More simply, you could have written (*)
Value2 := NativeUInt(MyArray2);
Let's continue with
Value1 := PUInt64(@MyArray1[0])^;
This is easy: MyArray1[0] is your 100, and you take the address and then dereference the pointer to get back the original value.
Value2 := PUInt64(@MyArray2[0])^;
Exact same reasoning here.
And
Value2 := PUInt64(MyArray2)^;
This is basically the case I mentioned above (at *), only dereferenced. It is valid in both 32-bit and 64-bit applications. (PUInt64 is of native size because of the P; it may be 32-bit.) Indeed, MyArray2 is a pointer to an UInt64, that is, a PUInt64, so by dereferencing it, you get that UInt64 value (100).
However,
Value1 := PUInt64(MyArray1)^;
is a bug. MyArray1 is, in your case, the exact same thing as a UInt64. It isn't a pointer to such a thing -- it isn't a PUInt64. Of course, you can lie to the compiler and tell it, "hey, treat this as a pointer to a UInt64". But by dereferencing it, you try to get the UInt64 at address 100, which will cause an access violation since you don't own that area in memory (most likely).
In a 64-bit process, the code compiles, since the sizes match. PUInt64 has the size of a pointer (64 bits), and MyArray1 has size 64 bits by your declaration.
In a 32-bit process, the code will not compile, since the sizes do not match. PUInt64 has the size of a pointer (32 bits), but MyArray1 has size 64 bits by your declaration.
Also it seems PUInt64(@MyArray2[0])^ is the safest cast since it works on both static and dynamic arrays. Is that correct?
Generally, I feel that static and dynamic arrays are two very different things, so I wouldn't attempt to force code to look the same in both cases.
Are you trying to get the first value in the array? If so, simply write MyArray2[0]. (Or MyArray1[0] in the static array case.) Are you trying to get the address of the first element? If so, I prefer NativeUInt(MyArray2) instead of NativeUInt(@MyArray2[0]) (or pointer, or PUInt64). Indeed, if the array is empty, the first method yields 0 or nil, while the other is a bug.
Update:
To clarify, if a is a static array, the address to the first element is simply @a (or @a[0]). If b is a dynamic array, the address to the first element is pointer(b) (or @b[0]). You may cast any pointer to some other pointer-sized type, like NativeUInt (integer type) or PUInt64 (specific pointer type) or PCardinal (specific pointer type) or ... . That will not change the actual value, only its compile-time interpretation.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With