Привести статический массив к указателю в Delphi? - PullRequest
3 голосов
/ 27 сентября 2019

В последнее время при использовании массивов 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;

В моем понимании статический массив хранитзначение первого элемента, второго элемента и т. д.в то время как динамический массив хранит адрес массива.

Следовательно, PUInt64 (@ MyArray2) ^ будет фактически содержать адрес массива в 64-битном компьютере, а половина из них содержит адрес в 32-битном компьютере.

Но почему PUInt64 (MyArray1) ^ является недопустимым приведением?

Также кажется, что PUInt64 (@ MyArray2 [0]) ^ является наиболее безопасным приведением, поскольку он работает как со статическими, так и с динамическими массивами.Это правильно?

1 Ответ

5 голосов
/ 27 сентября 2019

Прежде всего позвольте мне поблагодарить вас за написание такого хорошо изученного вопроса!

По сути, кажется, что вы поняли большинство из них правильно.В 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-битных приложениях.(PUInt64 имеет собственный размер из-за P; он может быть 32-разрядным.) Действительно, MyArray2 - это указатель на UInt64, то есть PUInt64поэтому, разыменовав его, вы получите значение UInt64 (100).

Однако

Value1 := PUInt64(MyArray1)^;

- ошибка.MyArray1 в вашем случае - это то же самое, что и UInt64.Это не указатель на такую ​​вещь - это не PUInt64.Конечно, вы можете лгать компилятору и говорить ему: «Эй, относитесь к этому как к указателю на UInt64».Но разыменовывая его, вы пытаетесь получить UInt64 по адресу 100, что приведет к нарушению доступа, поскольку у вас нет этой области в памяти (скорее всего).

В 64-битовый процесс, код компилируется, так как размеры совпадают.PUInt64 имеет размер указателя (64 бита), а MyArray1 имеет размер 64 бита по вашему объявлению.

В 32-битном процессе код не будет компилироваться, поскольку размеры нематч.PUInt64 имеет размер указателя (32 бита), но MyArray1 имеет размер 64 бита по вашему объявлению.

Также кажется, что PUInt64 (@ MyArray2 [0]) ^ является самым безопаснымприведение, поскольку он работает как на статических, так и на динамических массивах.Это правильно?

Как правило, я чувствую, что статические и динамические массивы - это две совершенно разные вещи, поэтому я не буду пытаться заставить код выглядеть одинаково в обоих случаях.

Вы пытаетесь получить первое значение в массиве?Если это так, просто напишите MyArray2[0].(Или MyArray1[0] в случае статического массива.) Вы пытаетесь получить адрес первого элемента?Если это так, я предпочитаю NativeUInt(MyArray2) вместо NativeUInt(@MyArray2[0]) (или pointer, или PUInt64).Действительно, если массив пуст, первый метод выдает 0 или nil, а другой - ошибку.

Обновление:

Для пояснения,если a является статическим массивом, адрес первого элемента просто @a (или @a[0]).Если b является динамическим массивом, адрес первого элемента равен pointer(b) (или @b[0]).Вы можете привести любой указатель к какому-либо другому типу размера указателя, например NativeUInt (целочисленный тип) или PUInt64 (определенный тип указателя) или PCardinal (определенный тип указателя) или ....Это не изменит фактическое значение, только его интерпретацию во время компиляции.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...