Esta é uma pergunta interessante!
Desde que Delete
altera o comprimento da matriz dinâmica - assim como SetLength
faz - ele precisa realocar a matriz dinâmica. E também altera o ponteiro dado a esse novo local na memória. Mas, obviamente, ele não pode alterar nenhum outro ponteiro para o antigo array dinâmico.
Portanto, ele deve diminuir a contagem de referência da antiga matriz dinâmica e criar uma nova matriz dinâmica com uma contagem de referência de 1. O ponteiro fornecido Delete
será definido para essa nova matriz dinâmica.
Portanto, o antigo array dinâmico deve ser intocado (exceto pela contagem reduzida de referências, é claro). Isso está essencialmente documentado para a SetLength
função semelhante :
Após uma chamada para SetLength
, S
é garantido que faça referência a uma string ou matriz exclusiva - ou seja, uma string ou matriz com uma contagem de referência de uma.
Mas, surpreendentemente, isso não acontece exatamente neste caso.
Considere este exemplo mínimo:
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;
Eu escolhi os valores para que sejam fáceis de localizar na memória (Alt + Ctrl + E).
Após (1), a
aponte para $02A2C198
minha execução de teste:
02A2C190 02 00 00 00 02 00 00 00
02A2C198 AA AA AA AA BB BB BB BB
Aqui, a contagem de referência é 2 e o comprimento da matriz é 2, conforme o esperado. (Consulte a documentação para o formato de dados interno para matrizes dinâmicas.)
Depois de (2) a = b
, isto é Pointer(a) = Pointer(b)
,. Ambos apontam para a mesma matriz dinâmica, que agora se parece com isso:
02A2C190 03 00 00 00 02 00 00 00
02A2C198 AA AA AA AA BB BB BB BB
Como esperado, a contagem de referência agora é 3.
Agora, vamos ver o que acontece depois (3). a
agora aponta para uma nova matriz dinâmica 2A30F88
no meu teste:
02A30F80 01 00 00 00 01 00 00 00
02A30F88 BB BB BB BB 01 00 00 00
Como esperado, esse novo array dinâmico possui uma contagem de referência de 1 e apenas o "elemento B".
Eu esperaria que a antiga matriz dinâmica, que b
ainda está apontando, parecesse como antes, mas com uma contagem de referência reduzida de 2. Mas parece com isso agora:
02A2C190 02 00 00 00 02 00 00 00
02A2C198 BB BB BB BB BB BB BB BB
Embora a contagem de referência seja realmente reduzida para 2, o primeiro elemento foi alterado.
Minha conclusão é que
(1) Faz parte do contrato do Delete
procedimento que ele invalida todas as outras referências à matriz dinâmica inicial.
ou
(2) Deve se comportar como descrevi acima, caso em que isso é um bug.
Infelizmente, a documentação para o Delete
procedimento não menciona isso.
Parece um bug.
Atualização: o código RTL
Eu dei uma olhada no código fonte do Delete
procedimento, e isso é bastante interessante.
Pode ser útil comparar o comportamento com o de SetLength
(porque esse funciona corretamente):
Se a contagem de referência da matriz dinâmica for 1, SetLength
tente simplesmente redimensionar o objeto de heap (e atualizar o campo de comprimento da matriz dinâmica).
Caso contrário, SetLength
cria uma nova alocação de heap para uma nova matriz dinâmica com uma contagem de referência de 1. A contagem de referência da matriz antiga é reduzida em 1.
Dessa forma, é garantido que a contagem de referência final seja sempre 1
- ou foi desde o início ou uma nova matriz foi criada. (É bom que você nem sempre faça uma nova alocação de heap. Por exemplo, se você tiver uma matriz grande com uma contagem de referência 1, simplesmente truncá-la é mais barato do que copiá-la para um novo local.)
Agora, como Delete
sempre reduz a matriz, é tentador tentar simplesmente reduzir o tamanho do objeto de heap onde está. E é realmente isso que o código RTL tenta System._DynArrayDelete
. Portanto, no seu caso, o BBBBBBBB
é movido para o início da matriz. Tudo está bem.
Mas então chama System.DynArraySetLength
, que também é usado por SetLength
. E este procedimento contém o seguinte comentário,
// If the heap object isn't shared (ref count = 1), just resize it. Otherwise, we make a copy
antes de detectar que o objeto é realmente compartilhado (no nosso caso, ref count = 3), faz uma nova alocação de heap para uma nova matriz dinâmica e copia a antiga (reduzida) para esse novo local. Reduz a contagem de ref da matriz antiga e atualiza a contagem de ref, o comprimento e o ponteiro de argumento da nova.
Então, acabamos com uma nova matriz dinâmica de qualquer maneira. Mas os programadores RTL esqueceu que já tinha foi cancelada a matriz original, que agora consiste na nova matriz colocada em cima do antigo: BBBBBBBB BBBBBBBB
.