9

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?

3
  • Use Value1 := PUInt64(MyArray1[0])^; instead of Value1 := PUInt64(MyArray1)^; Commented Sep 27, 2019 at 7:15
  • 2
    @SchneiderInfosystemsLtd: That's not a good idea. MyArray1[0] is a UInt64, not a pointer to a UInt64 (100 is not a valid address). And in a 32-bit process, the sizes won't even match (pointer is 32 bit, but MyArray1[0] is 64-bit). Commented Sep 27, 2019 at 9:04
  • @Andreas Rejjbrand: Yes, you are right,The PUInt64(MyArray1[0])^ is equal to PUInt64(100)^. That leads in a read access violation on address 0x064 (100). Commented Sep 27, 2019 at 11:44

1 Answer 1

9

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.

Sign up to request clarification or add additional context in comments.

1 Comment

Thank you very much for your explanation, which makes me understand this more clearly. Actually I hesitate to ask this question since it is not a complete real case. I encounter the problem in real case. Then I want to make further research on this instead of just letting my codes running. So I write the test snippet above to verify my opinions. What I need is just convert the array into a pointer and pass to a DLL written in C++. When casting using @ MyArray1, it is fine. But that does not work on @ MyArray2, finally I test with my test snippet and use PUInt64(@ MyArray2[0]).

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.