Đây là một câu hỏi thú vị!
Vì Delete
thay đổi độ dài của mảng động - cũng như SetLength
vậy - nó phải phân bổ lại mảng động. Và nó cũng thay đổi con trỏ được đặt cho vị trí mới này trong bộ nhớ. Nhưng rõ ràng nó không thể thay đổi bất kỳ con trỏ nào khác sang mảng động cũ.
Vì vậy, cần giảm số tham chiếu của mảng động cũ và tạo một mảng động mới với số tham chiếu là 1. Con trỏ được cung cấp Delete
sẽ được đặt thành mảng động mới này.
Do đó, mảng động cũ nên được xử lý (tất nhiên ngoại trừ số tham chiếu giảm của nó). Điều này về cơ bản là tài liệu cho SetLength
chức năng tương tự :
Theo một cuộc gọi đến SetLength
, S
được đảm bảo tham chiếu một chuỗi hoặc mảng duy nhất - nghĩa là một chuỗi hoặc mảng có số tham chiếu là một chuỗi.
Nhưng đáng ngạc nhiên, điều này không hoàn toàn xảy ra trong trường hợp này.
Xem xét ví dụ tối thiểu này:
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;
Tôi đã chọn các giá trị để chúng dễ dàng phát hiện trong bộ nhớ (Alt + Ctrl + E).
Sau (1), a
trỏ đến $02A2C198
trong lần chạy thử của tôi:
02A2C190 02 00 00 00 02 00 00 00
02A2C198 AA AA AA AA BB BB BB BB
Ở đây số tham chiếu là 2 và chiều dài mảng là 2, như mong đợi. (Xem tài liệu về định dạng dữ liệu nội bộ cho mảng động.)
Sau (2) a = b
, nghĩa là , Pointer(a) = Pointer(b)
. Cả hai đều trỏ đến cùng một mảng động, hiện tại trông như thế này:
02A2C190 03 00 00 00 02 00 00 00
02A2C198 AA AA AA AA BB BB BB BB
Theo dự kiến, số tham chiếu bây giờ là 3.
Bây giờ, hãy xem điều gì xảy ra sau (3). a
bây giờ trỏ đến một mảng động mới 2A30F88
trong lần chạy thử của tôi:
02A30F80 01 00 00 00 01 00 00 00
02A30F88 BB BB BB BB 01 00 00 00
Như mong đợi, mảng động mới này có số tham chiếu là 1 và chỉ có "phần tử B".
Tôi hy vọng mảng động cũ, b
vẫn còn trỏ đến, trông giống như trước đây nhưng với số tham chiếu giảm là 2. Nhưng bây giờ nó trông như thế này:
02A2C190 02 00 00 00 02 00 00 00
02A2C198 BB BB BB BB BB BB BB BB
Mặc dù số tham chiếu thực sự giảm xuống còn 2, yếu tố đầu tiên đã được thay đổi.
Kết luận của tôi là
(1) Đây là một phần của hợp đồng của Delete
thủ tục mà nó làm mất hiệu lực tất cả các tham chiếu khác đến mảng động ban đầu.
hoặc là
(2) Nó sẽ hoạt động như tôi đã nêu ở trên, trong trường hợp này là một lỗi.
Thật không may, tài liệu cho Delete
thủ tục không đề cập gì cả.
Nó cảm thấy như một lỗi.
Cập nhật: Mã RTL
Tôi đã xem mã nguồn của Delete
thủ tục và điều này khá thú vị.
Có thể hữu ích khi so sánh hành vi với hành vi của SetLength
(vì hành vi đó hoạt động chính xác):
Nếu số tham chiếu của mảng động là 1, SetLength
chỉ cần thử thay đổi kích thước đối tượng heap (và cập nhật trường độ dài của mảng động).
Mặt khác, SetLength
thực hiện phân bổ heap mới cho mảng động mới với số tham chiếu là 1. Số tham chiếu của mảng cũ bị giảm đi 1.
Bằng cách này, đảm bảo rằng số tham chiếu cuối cùng luôn luôn 1
- có thể là từ đầu hoặc một mảng mới đã được tạo. (Một điều tốt là bạn không luôn luôn thực hiện phân bổ heap mới. Ví dụ: nếu bạn có một mảng lớn với số tham chiếu là 1, chỉ cần cắt bớt nó sẽ rẻ hơn so với sao chép nó sang một vị trí mới.)
Bây giờ, vì Delete
luôn làm cho mảng nhỏ hơn, nên cố gắng đơn giản là giảm kích thước của đối tượng heap nơi nó đang ở. Và đây thực sự là những gì mã RTL cố gắng System._DynArrayDelete
. Do đó, trong trường hợp của bạn, phần BBBBBBBB
được chuyển đến phần đầu của mảng. Tất cả đều tốt.
Nhưng sau đó nó gọi System.DynArraySetLength
, cũng được sử dụng bởi SetLength
. Và thủ tục này có chứa các bình luận sau đây,
// If the heap object isn't shared (ref count = 1), just resize it. Otherwise, we make a copy
trước khi nó phát hiện ra rằng đối tượng thực sự được chia sẻ (trong trường hợp của chúng tôi, số lượng ref = 3), thực hiện phân bổ heap mới cho một mảng động mới và sao chép một cái cũ (đã giảm) vào vị trí mới này. Nó làm giảm số lượng ref của mảng cũ và cập nhật số ref, chiều dài và con trỏ đối số của cái mới.
Vì vậy, chúng tôi đã kết thúc với một mảng động mới. Nhưng các lập trình viên RTL quên rằng họ đã làm hỏng mảng ban đầu, hiện bao gồm mảng mới được đặt trên đầu của mảng cũ : BBBBBBBB BBBBBBBB
.