Tudo bem, acho que todos concordamos que o que acontece com o código a seguir é indefinido, dependendo do que foi passado,
void deleteForMe(int* pointer)
{
delete[] pointer;
}
O ponteiro pode ter todos os tipos de coisas diferentes e, portanto, executar um incondicional delete[]
nele é indefinido. No entanto, vamos supor que estamos realmente passando um ponteiro de matriz,
int main()
{
int* arr = new int[5];
deleteForMe(arr);
return 0;
}
Minha pergunta é, neste caso, onde o ponteiro é uma matriz, quem é que sabe disso? Quero dizer, do ponto de vista da linguagem / compilador, não tem idéia se é ou não arr
um ponteiro de matriz versus um ponteiro para um único int. Caramba, ele nem sabe se arr
foi criado dinamicamente. No entanto, se eu fizer o seguinte,
int main()
{
int* num = new int(1);
deleteForMe(num);
return 0;
}
O sistema operacional é inteligente o suficiente para excluir apenas um int e não continuar com algum tipo de 'matança', excluindo o restante da memória além desse ponto (em contraste com isso com strlen
uma \0
string não terminada - ele continuará até atinge 0).
Então, de quem é o trabalho para lembrar dessas coisas? O sistema operacional mantém algum tipo de registro em segundo plano? (Quero dizer, eu percebi que comecei este post dizendo que o que acontece é indefinido, mas o fato é que o cenário de 'matança' não acontece, portanto, no mundo prático, alguém está se lembrando.)
Respostas:
O compilador não sabe que é uma matriz, está confiando no programador. Excluir um ponteiro para um
int
comdelete []
resultaria em um comportamento indefinido. Seu segundomain()
exemplo é inseguro, mesmo se não travar imediatamente.O compilador precisa acompanhar quantos objetos precisam ser excluídos de alguma forma. Isso pode ser superalocado o suficiente para armazenar o tamanho da matriz. Para mais detalhes, consulte as Super Perguntas frequentes do C ++ .
fonte
Uma pergunta que as respostas dadas até agora não parecem abordar: se as bibliotecas de tempo de execução (não o sistema operacional, na verdade) podem acompanhar o número de coisas na matriz, por que precisamos da
delete[]
sintaxe? Por que não édelete
possível usar um único formulário para lidar com todas as exclusões?A resposta para isso remonta às raízes do C ++ como uma linguagem compatível com C (que já não se esforça mais para ser). A filosofia do Stroustrup era que o programador não deveria pagar por nenhum recurso que não estivesse usando. Se eles não estiverem usando matrizes, não deverão suportar o custo de matrizes de objetos para cada pedaço de memória alocado.
Ou seja, se o seu código simplesmente
o espaço de memória alocado
foo
não deve incluir nenhuma sobrecarga extra necessária para suportar matrizes deFoo
.Como apenas as alocações de matriz são configuradas para transportar informações adicionais sobre o tamanho da matriz, você precisa solicitar às bibliotecas de tempo de execução que procurem essas informações ao excluir os objetos. É por isso que precisamos usar
em vez de apenas
se bar é um ponteiro para uma matriz.
Para a maioria de nós (inclusive eu), essa confusão sobre alguns bytes extras de memória parece estranha hoje em dia. Mas ainda existem situações em que salvar alguns bytes (do que poderia ser um número muito alto de blocos de memória) pode ser importante.
fonte
Sim, o sistema operacional mantém algumas coisas em segundo plano. Por exemplo, se você executar
o sistema operacional pode alocar 4 bytes extras, armazenar o tamanho da alocação nos primeiros 4 bytes da memória alocada e retornar um ponteiro de deslocamento (ou seja, aloca espaços de memória de 1000 a 1024, mas o ponteiro retorna pontos para 1004, com locais de 1000 a 1003 armazenando o tamanho da alocação). Em seguida, quando a exclusão é chamada, ele pode observar 4 bytes antes que o ponteiro passe para encontrar o tamanho da alocação.
Estou certo de que existem outras maneiras de rastrear o tamanho de uma alocação, mas essa é uma opção.
fonte
for(int i = 0; i < *(arrayPointer - 1); i++){ }
Isso é muito semelhante a esta pergunta e tem muitos dos detalhes que você está procurando.
Mas basta dizer que não é tarefa do sistema operacional rastrear nada disso. Na verdade, são as bibliotecas de tempo de execução ou o gerenciador de memória subjacente que rastreiam o tamanho da matriz. Isso geralmente é feito alocando memória extra antecipadamente e armazenando o tamanho da matriz nesse local (a maioria usa um nó principal).
Isso é visível em algumas implementações executando o seguinte código
fonte
size_t size = *(reinterpret_cast<size_t *>(pArray) - 1)
vez dissodelete
oudelete[]
provavelmente liberaria a memória alocada (memória apontada), mas a grande diferença é quedelete
em uma matriz não chamará o destruidor de cada elemento da matriz.Enfim, misturando
new/new[]
edelete/delete[]
é provavelmente UB.fonte
Não sabe que é uma matriz, é por isso que você precisa fornecer, em
delete[]
vez da idade normaldelete
.fonte
Eu tive uma pergunta semelhante a isso. Em C, você aloca memória com malloc () (ou outra função semelhante) e a exclui com free (). Existe apenas um malloc (), que simplesmente aloca um certo número de bytes. Existe apenas um free (), que simplesmente aceita um ponteiro como parâmetro.
Então, por que em C você pode simplesmente entregar o ponteiro para liberar, mas em C ++ você deve dizer se é uma matriz ou uma única variável?
A resposta, eu aprendi, tem a ver com destruidores de classe.
Se você alocar uma instância de uma classe MyClass ...
E exclua-o com delete, você pode obter apenas o destruidor da primeira instância do MyClass chamada. Se você usar delete [], pode ter certeza de que o destruidor será chamado para todas as instâncias da matriz.
Esta é a diferença importante. Se você está simplesmente trabalhando com tipos padrão (por exemplo, int), não verá esse problema. Além disso, você deve se lembrar que o comportamento do uso de excluir em novo [] e excluir [] em novo não é definido - ele pode não funcionar da mesma maneira em todos os compiladores / sistemas.
fonte
Cabe ao tempo de execução o responsável pela alocação de memória, da mesma maneira que você pode excluir uma matriz criada com malloc no padrão C usando gratuitamente. Eu acho que cada compilador implementa de maneira diferente. Uma maneira comum é alocar uma célula extra para o tamanho da matriz.
No entanto, o tempo de execução não é inteligente o suficiente para detectar se é ou não uma matriz ou um ponteiro, é necessário informá-lo e, se você estiver enganado, não exclui corretamente (por exemplo, ptr em vez de matriz) ou você acaba assumindo um valor não relacionado ao tamanho e causa danos significativos.
fonte
Uma das abordagens para compiladores é alocar um pouco mais de memória e armazenar a contagem de elementos no elemento principal.
Exemplo de como isso pode ser feito: Aqui
O compilador alocará sizeof (int) * 5 bytes.
Armazenará
4
nos primeirossizeof(int)
bytesE definir
i
Então,
i
aponta para a matriz de 4 elementos, não 5.E
será processado da seguinte maneira
fonte
Semanticamente, as duas versões do operador de exclusão no C ++ podem "comer" qualquer ponteiro; no entanto, se um ponteiro para um único objeto for fornecido
delete[]
, o UB resultará, significando que tudo pode acontecer, incluindo uma falha no sistema ou nada.O C ++ exige que o programador escolha a versão correta do operador de exclusão, dependendo do assunto da desalocação: matriz ou objeto único.
Se o compilador pudesse determinar automaticamente se um ponteiro passado para o operador de exclusão era uma matriz de ponteiros, haveria apenas um operador de exclusão no C ++, o que seria suficiente para ambos os casos.
fonte
Concorde que o compilador não sabe se é uma matriz ou não. Depende do programador.
Às vezes, o compilador monitora quantos objetos precisam ser excluídos, alocando em excesso o suficiente para armazenar o tamanho da matriz, mas nem sempre necessário.
Para obter uma especificação completa quando o armazenamento extra é alocado, consulte C ++ ABI (como os compiladores são implementados): Itanium C ++ ABI: Array Operator new Cookies
fonte
Você não pode usar excluir para uma matriz e não pode usar excluir [] para uma não matriz.
fonte
"comportamento indefinido" significa simplesmente que a especificação do idioma não garante o que acontecerá. Isso geralmente não significa que algo ruim irá acontecer.
Normalmente existem duas camadas aqui. O gerenciador de memória subjacente e a implementação do C ++.
Em geral, o gerenciador de memória lembrará (entre outras coisas) o tamanho do bloco de memória que foi alocado. Isso pode ser maior que o bloco solicitado pela implementação do C ++. Normalmente, o gerenciador de memória armazena seus metadados antes do bloco de memória alocado.
A implementação do C ++ geralmente lembra apenas o tamanho da matriz se for necessário para seus próprios fins, geralmente porque o tipo possui um destruidor não-trival.
Portanto, para tipos com um destruidor trivial, a implementação de "delete" e "delete []" é normalmente a mesma. A implementação C ++ simplesmente passa o ponteiro para o gerenciador de memória subjacente. Algo como
Por outro lado, para tipos com um destruidor não trivial, "delete" e "delete []" provavelmente serão diferentes. "delete" seria algo parecido (onde T é o tipo que o ponteiro aponta)
Enquanto "delete []" seria algo parecido.
fonte
itere através da matriz de objetos e chame destruidor para cada um deles. Eu criei esse código simples que sobrecarrega novas expressões [] e exclui [] e fornece uma função de modelo para desalocar memória e chamar destruidor para cada objeto, se necessário:
fonte
apenas defina um destruidor dentro de uma classe e execute seu código com as duas sintaxes
de acordo com a saída você pode encontrar as soluções
fonte
A resposta:
int * pArray = novo int [5];
int tamanho = * (pArray-1);
Postado acima não está correto e produz valor inválido. O "-1" conta os elementos No sistema operacional Windows de 64 bits, o tamanho correto do buffer reside no endereço Ptr - 4 bytes
fonte