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