Это интересный вопрос!
Поскольку Delete
меняет длину массива dynamici c - так же, как SetLength
- он должен перераспределить массив Dynami c. И это также изменяет указатель, данный ему на это новое место в памяти. Но очевидно, что он не может изменить любые другие указатели на старый массив Dynami c.
Поэтому он должен уменьшить счетчик ссылок старого массива Dynami c и создать новый массив Dynami c со ссылкой число 1. Указатель, заданный для Delete
, будет установлен на этот новый массив Dynami c.
Следовательно, старый массив Dynami c должен быть нетронутым (за исключением его уменьшенного числа ссылок, курс). Это задокументировано для аналогичной SetLength
функции :
После вызова SetLength
, S
гарантированно ссылается на уникальную строку или массив - что is, строка или массив с количеством ссылок, равным единице.
Но, что удивительно, в данном случае это не совсем так.
Рассмотрим этот минимальный пример:
procedure TForm1.FormCreate(Sender: TObject);
var
a, b: array of Integer;
begin
a := [$AAAAAAAA, $BBBBBBBB]; {1}
b := a; {2}
Delete(a, 0, 1); {3}
end;
Я выбрал значения, чтобы их было легко найти в памяти (Alt + Ctrl + E).
После (1) a
указывает на $02A2C198
в моем тестовом прогоне:
02A2C190 02 00 00 00 02 00 00 00
02A2C198 AA AA AA AA BB BB BB BB
Здесь счетчик ссылок равен 2, а длина массива равна 2, как и ожидалось. (См. Документацию для внутреннего формата данных для динамических c массивов.)
После (2), a = b
, то есть Pointer(a) = Pointer(b)
. Они оба указывают на один и тот же динамический массив c, который теперь выглядит следующим образом:
02A2C190 03 00 00 00 02 00 00 00
02A2C198 AA AA AA AA BB BB BB BB
Как и ожидалось, счетчик ссылок теперь равен 3.
Теперь посмотрим, что произойдет после (3). a
теперь указывает на новый динамический c массив в 2A30F88
в моем тестовом прогоне:
02A30F80 01 00 00 00 01 00 00 00
02A30F88 BB BB BB BB 01 00 00 00
Как и ожидалось, этот новый динамический c массив имеет счетчик ссылок 1 и только «Элемент B».
Я бы ожидал, что старый массив Dynami c, на который все еще указывает b
, будет выглядеть так же, как и раньше, но с уменьшенным количеством ссылок 2. Но теперь это выглядит так :
02A2C190 02 00 00 00 02 00 00 00
02A2C198 BB BB BB BB BB BB BB BB
Хотя счетчик ссылок действительно уменьшен до 2, первый элемент был изменен.
Мой вывод таков:
(1) Он является частью контракт процедуры Delete
о том, что он делает недействительными все другие ссылки на исходный массив Dynami c.
или
(2) Он должен вести себя так, как я обрисовал выше, в этом случае это ошибка.
К сожалению, документация для Delete
процедуры вообще не упоминает об этом.
Это похоже на ошибку.
Обновление: код RTL
Я посмотрел исходный код процедуры Delete
, и это довольно интересно.
Может быть полезно сравнить поведение с SetLength
(потому что это работает правильно):
Если счетчик ссылок Dynami c массив равен 1, SetLength
пытается просто изменить размер объекта кучи (и обновить поле длины массива Dynami c).
В противном случае SetLength
делает новое выделение кучи для нового динамического c массива со счетчиком ссылок 1. Счетчик ссылок старого массива уменьшается на 1.
Таким образом, гарантируется, что окончательный счетчик ссылок всегда равен 1
- либо с самого начала, либо был создан новый массив. (Хорошо, что вы не всегда делаете новое выделение кучи. Например, если у вас большой массив со счетчиком ссылок, равным 1, просто укоротить его дешевле, чем скопировать его в новое место.)
Теперь, поскольку Delete
всегда уменьшает размер массива, заманчиво попытаться просто уменьшить размер кучи, где он находится. И это действительно то, что пытается сделать код RTL в System._DynArrayDelete
. Следовательно, в вашем случае BBBBBBBB
перемещается в начало массива. Все хорошо.
Но тогда он вызывает System.DynArraySetLength
, что также используется SetLength
. И эта процедура содержит следующий комментарий,
// If the heap object isn't shared (ref count = 1), just resize it. Otherwise, we make a copy
прежде чем он обнаружит, что объект действительно является общим (в нашем случае ref count = 3), сделает новое выделение кучи для нового динамического c массива и скопирует старый (уменьшенный) в это новое местоположение. Он уменьшает счетчик ссылок старого массива и обновляет счетчик ссылок, длину и указатель аргумента нового.
Таким образом, мы все равно получили новый динамический массив c. Но программисты RTL забыли, что они уже испортили исходный массив, который теперь состоит из нового массива, помещенного поверх старого: BBBBBBBB BBBBBBBB
.