TArray Результат не всегда изначально() в цикле for?

Result в тесте НЕ всегда изначально ()

Я нашел Нужно ли устанавливать длину динамического массива при инициализации?, однако я не совсем понимаю этот ответ

Что еще более важно, как лучше всего «разорвать» соединение Result/A (мне нужен цикл)? Возможно, какой-то способ заставить компилятор "правильно" инициализироваться? Вручную добавить Result := nil в качестве первой строки в тесте?

function Test(var A: TArray<Integer>): TArray<Integer>;
begin
  SetLength(Result, 3); // Breakpoint here
  Result[0] := 2;
end;

procedure TForm1.Button3Click(Sender: TObject);
var
  A: TArray<Integer>; // Does not help whether A is local or global
  I: Integer;
begin
  for I := 1 to 3 do
    A := Test(A); // At Test breakpoint:
                  // * FIRST loop: Result is ()
                  // * NEXT loops: Result is (2, 0, 0)
                  //               modifying Result changes A (immediately)
  A := Test(A);   // Result is again ()
end;

В вашей точке останова массив Result еще не инициализирован. Почему вы передаете «A» в качестве аргумента var в Test( если вы его не изменяете? В вашем случае, делая A := Test(A); это бессмыслица. Содержимое локальной переменной не определено, пока им не будет присвоено значение .

Marcodor 10.04.2023 16:59

Маркодор: это всего лишь MRE, не ожидайте, что это будет иметь слишком много смысла; несмотря на это, начиная с цикла for #2, Result IS инициализируется в точке останова, см. комментарии в коде

hundreAd 10.04.2023 17:01

Нет, он только кажется инициализированным. Он просто повторно использовал один и тот же адрес памяти, без гарантии, что это всегда будет происходить. Вы всегда должны инициализировать локальные переменные. См.: docwiki.embarcadero.com/RADStudio/Sydney/en/Variables_(Delph‌​i)

Marcodor 10.04.2023 17:06

Маркодор: не согласно stackoverflow.com/questions/5314918/…: «Динамические массивы являются управляемыми типами и поэтому всегда инициализируются нулем»; исключение: «компилятор в качестве оптимизации выбирает не повторно инициализировать неявную локальную переменную внутри цикла»

hundreAd 10.04.2023 17:10

Да, управляемые типы инициализируются, но не возвращаемые значения Result/function.

Marcodor 10.04.2023 17:36

Я не понимаю вашей проблемы. В первом цикле A пусто до выполнения теста и [2,0,0] после выполнения. var A причины modifying Result changes A (immediately)

MBo 10.04.2023 17:36

MBo: конечно, A будет присвоен результат ПОСЛЕ теста, но НЕ сразу очевидно, что изменение Result в точке останова НЕМЕДЛЕННО повлияет на него

hundreAd 10.04.2023 18:02

Маркодор: насколько я понимаю (что может быть неправильным, отсюда и этот вопрос), управляемые типы результатов, такие как целые числа, динамические массивы и т. д., ЯВЛЯЮТСЯ «правильно» инициализированными; это крайний случай, когда этого не происходит из-за того, что он находится внутри цикла for (обратите внимание, что последний тестовый вызов после цикла ведет себя «как и ожидалось»)

hundreAd 10.04.2023 18:07

Результат никогда не инициализируется. Integer не является управляемым типом.

Marcodor 10.04.2023 18:42

Это предполагаемое поведение, оптимизация производительности. Есть несколько статей об этом, которые я попытаюсь найти.

David Heffernan 10.04.2023 18:46
stackoverflow.com/a/5315254/505088
David Heffernan 10.04.2023 18:49

Я думаю, что мы, вероятно, могли бы закрыть этот вопрос как дубликат того, на который я ссылался.

David Heffernan 10.04.2023 18:49

Дэвид Хеффернан: на самом деле я упомянул об этом в исходном вопросе. я до сих пор не понимаю, можно ли отключить эту оптимизацию или как лучше всего решить/избежать эту проблему

hundreAd 10.04.2023 18:51
Структурированный массив Numpy
Структурированный массив Numpy
Однако в реальных проектах я чаще всего имею дело со списками, состоящими из нескольких типов данных. Как мы можем использовать массивы numpy, чтобы...
T - 1Bits: Генерация последовательного массива
T - 1Bits: Генерация последовательного массива
По мере того, как мы пишем все больше кода, мы привыкаем к определенным способам действий. То тут, то там мы находим код, который заставляет нас...
Что такое деструктуризация массива в JavaScript?
Что такое деструктуризация массива в JavaScript?
Деструктуризация позволяет распаковывать значения из массивов и добавлять их в отдельные переменные.
0
13
128
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Упомянутый вопрос касается полей внутри класса, и все они инициализированы нулями, а управляемые типы должным образом завершаются во время уничтожения экземпляра.

Ваш код предназначен для вызова функции с управляемым типом возврата в цикле. Локальная переменная управляемого типа инициализируется один раз - в начале подпрограммы. Управляемый возвращаемый тип под капотом обрабатывается компилятором как параметр var. Таким образом, после первого вызова он дважды передает то, что выглядит как A, в Test — как параметр A и для Result.

Но ваша оценка того, что изменение Result также влияет на A (параметр), неверна, что мы можем доказать, немного изменив код:

function Test(var A: TArray<Integer>; I: Integer): TArray<Integer>;
begin
  SetLength(Result, 3); // Breakpoint here
  Result[0] := I;
end;

procedure Main;
var
  A: TArray<Integer>;
  I: Integer;
begin
  for I := 1 to 3 do
    A := Test(A, I);
                    
  A := Test(A, 0);
end;

Когда вы сделаете один шаг через Test, вы увидите, что изменение Result[0] не изменит A. Это потому, что SetLength создаст копию, потому что компилятор ввел вторую переменную, которую он временно использует для передачи результата, и после вызова Test он присваивает ее A (локальной переменной) — вы можете видеть это в представлении дизассемблирования, которое будет выглядеть аналогично к этому для строки в цикле (я использую $O+, чтобы сделать код немного плотнее, чем он был бы без оптимизации):

Project1.dpr.21: A := Test(A, I);
0041A3BD 8D4DF8           lea ecx,[ebp-$08]
0041A3C0 8D45FC           lea eax,
0041A3C3 8BD3             mov edx,ebx
0041A3C5 E8B2FFFFFF       call Test
0041A3CA 8B55F8           mov edx,[ebp-$08]
0041A3CD 8D45FC           lea eax,[ebp-$04]
0041A3D0 8B0DC8244000     mov ecx,[$004024c8]
0041A3D6 E855E7FEFF       call @DynArrayAsg
0041A3DB 43               inc ebx

Зная соглашение о вызовах по умолчанию — это первые три параметра в eax, edx и ecx, мы знаем, что eax — это параметр A, edx — это I, а ecx — это Result (вышеупомянутый параметр Result var всегда является последним). Мы видим, что он использует разные места в стеке ([ebp-$04] это переменная A и [ebp-$08] переменная, введенная компилятором). И после вызова мы видим, что компилятор вставил дополнительный вызов System._DynArrayAsg, который затем назначает введенную компилятором временную переменную для Result на A.

Вот скриншот второго вызова Test:

ваш анализ неверен: согласно отладчику (и то, что ОНА говорит, идет! ;) модификация Result в точке останова НЕМЕДЛЕННО модифицирует A -- начиная со 2-го цикла; это, что неудивительно, также происходит с вашим измененным кодом

hundreAd 11.04.2023 14:35

@hundreAd, не могли бы вы еще раз медленно прочитать ответ?

Marcodor 11.04.2023 16:55

Стефан Глиенке: нельзя публиковать скриншоты в комментариях (афаик?), но, тем не менее, ваша точка останова другая: она ПОСЛЕ SetLength, посмотрите, что происходит ДО ее выполнения

hundreAd 12.04.2023 15:46

Стефан Глиенке: это может быть случай, когда MRE слишком прост, чтобы представить сложность фактического кода, мне придется продолжить расследование, чтобы увидеть, действительно ли существует случай, когда A «на месте» перезаписывается изменениями в Result. (процедура генерирует результат на основе A и, наконец, присваивает результат A, но A должен оставаться неизменным в течение этого процесса)

hundreAd 12.04.2023 16:08

Стефан Глиенке: попробуйте добавить в качестве первой строки в тесте: if Length(Result)>0 then Result[0] := 66;

hundreAd 12.04.2023 16:41
Ответ принят как подходящий

Хотя я не решаюсь назвать эту For оптимизацию компилятора ошибкой, это, безусловно, бесполезно при непосредственном изменении элементов массива:

function Test(var A: TArray<Integer>): TArray<Integer>;
begin
  if Length(Result) > 0 then // Breakpoint
    Result[1] := 66; // A modified!
  SetLength(Result, 3);
  Result[0] := Result[0] + 1; // A not modified
  Exit;
  A[9] := 666; // Force linker not to eliminate A
end;

После исследования я пришел к выводу, что функции, влияющие на весь массив (например, SetLength, Copy или какая-то другая функция, возвращающая TArray<Integer>), неудивительно, что «нарушат» идентичность Результат/А, созданную циклом For.

Казалось бы, самый безопасный подход (согласно ответу, указанному в исходном посте) — Result := nil; в качестве первой строки в тесте.

Если нет дальнейших предложений, я в конечном итоге приму это как ответ.

ПРИМЕЧАНИЕ: В качестве дополнительного бонуса, начиная с Result := nil, массив не будет скопирован с помощью SetLength — очевидно, но, например, для массив из 100000 зацикливается 100000 раз, эта небольшая модификация приводит к сокращению времени выполнения примерно на 40%

Я уже объяснил в своем ответе, что SetLength создает копию - конечно, она изменит A, если вы напишете Result перед вызовом SetLength, потому что динамические массивы не имеют копирования при записи, как строки. Кроме того, это должно быть изменением вашего вопроса, а не ответом.

Stefan Glienke 12.04.2023 17:22

Стефан Глиенке: не модификация, а просто уточнение - похоже, вы не изменили значения (в точке останова) через отладчик. несмотря на это, это вызвано тем, что цикл for не повторно инициализирует Result, иначе код вел бы себя так, как [i, по крайней мере] ожидалось; ответ предназначен для того, чтобы решить эту проблему, чего не сделал ваш пост

hundreAd 12.04.2023 17:33

Я никогда не понимал исходный комментарий modifying Result changes A (immediately) как «изменение некоторого значения в Результате с помощью отладчика» - поэтому в вашем вопросе отсутствует информация, которую знаете только вы.

Stefan Glienke 12.04.2023 18:17

Стефан Глиенке: я (очевидно, ошибочно) полагал, что все ясно: значение Result[?] должно быть изменено до выполнения инструкции в точке останова. делаете ли вы это с помощью кода, с помощью отладчика или любым другим способом... я исправлен и постараюсь быть еще более ясным в следующий раз; что касается комментария immediately - не очень очевидно, что изменение Result в точке останова также изменит A (после завершения функции Result, конечно, будет присвоен A)

hundreAd 12.04.2023 18:42

Другие вопросы по теме