É seguro excluir um ponteiro vazio?

Suponha que eu tenha o seguinte código:

void* my_alloc (size_t size) { return new char [size]; } void my_free (void* ptr) { delete [] ptr; } 

Isso é seguro? Ou deve ptr ser convertido em char* antes da exclusão?

Depende de “seguro”. Ele geralmente funciona porque as informações são armazenadas junto com o ponteiro sobre a própria alocação, portanto, o desalocador pode retorná-lo ao local correto. Nesse sentido, é “seguro”, desde que seu alocador use tags de limite interno. (Muitos fazem)

No entanto, como mencionado acima, a exclusão de um ponteiro vazio não chamará destruidores, o que pode ser um problema. Nesse sentido, não é “seguro”.

Não há uma boa razão para fazer o que você está fazendo do jeito que você está fazendo. Se você deseja escrever suas próprias funções de desalocação, você pode usar modelos de function para gerar funções com o tipo correto. Uma boa razão para isso é gerar alocadores de pool, que podem ser extremamente eficientes para tipos específicos.

Como mencionado em outras respostas, isso é um comportamento indefinido em C ++. Em geral, é bom evitar um comportamento indefinido, embora o tópico em si seja complexo e repleto de opiniões conflitantes.

A exclusão por meio de um ponteiro vazio é indefinida pelo padrão C ++ – consulte a seção 5.3.5 / 3:

Na primeira alternativa (delete object), se o tipo estático do operando for diferente do seu tipo dynamic, o tipo estático deve ser uma class base do tipo dynamic do operando e o tipo estático deve ter um destruidor virtual ou o comportamento é indefinido . Na segunda alternativa (excluir matriz), se o tipo dynamic do object a ser excluído for diferente de seu tipo estático, o comportamento será indefinido.

E sua nota de rodapé:

Isso implica que um object não pode ser excluído usando um ponteiro do tipo void * porque não há objects do tipo void

.

Não é uma boa ideia e não é algo que você faria em C ++. Você está perdendo suas informações de tipo sem nenhum motivo.

Seu destrutor não será chamado nos objects em sua matriz que você está excluindo quando o chamar para tipos não primitivos.

Você deve replace o novo / excluir.

Excluir o vazio * provavelmente irá liberar sua memory corretamente por acaso, mas está errado porque os resultados são indefinidos.

Se, por algum motivo, você não souber precisar armazenar seu ponteiro em um vazio e depois liberá-lo, você deve usar malloc e free.

A exclusão de um ponteiro vazio é perigosa porque os destruidores não serão chamados no valor para o qual ele realmente aponta. Isso pode resultar em vazamentos de memory / resources em seu aplicativo.

Porque o char não possui uma lógica especial de destruição. Isso não vai funcionar.

 class foo { ~foo() { printf("huzza"); } } main() { foo * myFoo = new foo(); delete ((void*)foo); } 

O d’ctor não será chamado.

Se você quiser usar void *, por que você não usa apenas malloc / free? new / delete é mais do que apenas gerenciamento de memory. Basicamente, new / delete chama um construtor / destrutor e há mais coisas acontecendo. Se você apenas usar tipos embutidos (como char *) e apagá-los através de void *, isso funcionaria, mas ainda assim não é recomendado. A linha de fundo é usar malloc / free se você quiser usar void *. Caso contrário, você pode usar funções de modelo para sua conveniência.

 template T* my_alloc (size_t size) { return new T [size]; } template void my_free (T* ptr) { delete [] ptr; } int main(void) { char* pChar = my_alloc(10); my_free(pChar); } 

Se você realmente precisa fazer isso, por que não cortar o intermediário (os new operadores e delete ) e chamar o operator new global de operator new e o operator delete diretamente? (Claro, se você está tentando instrumentar os operadores new e delete , você deveria reimplementar o operator new e operator delete .)

 void* my_alloc (size_t size) { return ::operator new(size); } void my_free (void* ptr) { ::operator delete(ptr); } 

Note que, diferentemente de malloc() , operator new lança std::bad_alloc na falha (ou chama o new_handler se estiver registrado).

A pergunta não faz sentido. Sua confusão pode ser em parte devido à linguagem desleixada que as pessoas costumam usar com delete :

Você usa delete para destruir um object que foi alocado dinamicamente. Faça isso, você forma uma expressão de exclusão com um ponteiro para esse object . Você nunca “apaga um ponteiro”. O que você realmente faz é “apagar um object que é identificado pelo seu endereço”.

Agora vemos porque a pergunta não faz sentido: um ponteiro vazio não é o “endereço de um object”. É apenas um endereço, sem qualquer semântica. Pode ter vindo do endereço de um object real, mas essa informação é perdida, porque foi codificada no tipo do ponteiro original. A única maneira de restaurar um ponteiro de object é converter o ponteiro vazio de volta em um ponteiro de object (o que requer que o autor saiba o que o ponteiro significa). void si é um tipo incompleto e, portanto, nunca o tipo de um object, e um ponteiro vazio nunca pode ser usado para identificar um object. (Objetos são identificados conjuntamente por seu tipo e seu endereço.)

Muitas pessoas já comentaram dizendo que não, não é seguro excluir um ponteiro vazio. Concordo com isso, mas também queria acrescentar que, se você está trabalhando com pointers nulos para alocar matrizes contíguas ou algo semelhante, você pode fazer isso com o new para poder usar a delete com segurança (com , um pouco de trabalho extra). Isso é feito alocando um ponteiro vazio para a região da memory (chamada de ‘arena’) e, em seguida, fornecendo o ponteiro para a arena para novo. Veja esta seção na FAQ do C ++ . Essa é uma abordagem comum para implementar conjuntos de memory em C ++.

Não há praticamente uma razão para fazer isso.

Primeiro de tudo, se você não sabe o tipo de dados, e tudo que você sabe é que é void* , então você deveria tratar os dados como um blob tip de dados binários ( unsigned char* ), e usar malloc / free para lidar com isso. Isso é necessário às vezes para coisas como dados de forma de onda e similares, onde você precisa passar os pointers void* para C apis. Isso é bom.

Se você sabe o tipo dos dados (isto é, se tem um ctor / dtor), mas por algum motivo você acabou com um void* pointer (por qualquer razão que você tenha) então você realmente deveria converter para o tipo que você conhece que seja , e chamar delete nele.

Eu usei void *, (também conhecido como tipos desconhecidos) em minha estrutura durante a reflection de código e outras proezas de ambigüidade, e até agora, não tive problemas (memory leaks, violações de access, etc.) de nenhum compilador. Apenas avisos devido à operação ser não padrão.

Faz sentido excluir um desconhecido (void *). Apenas certifique-se de que o ponteiro siga essas diretrizes ou pare de fazer sentido:

1) O ponteiro desconhecido não deve apontar para um tipo que tenha um desconstrutor trivial e, portanto, quando for lançado como um ponteiro desconhecido, NUNCA DEVE SER APAGADO. Exclua somente o ponteiro desconhecido APÓS lançá-lo de volta ao tipo ORIGINAL.

2) A instância está sendo referenciada como um ponteiro desconhecido na memory vinculada à pilha ou no limite de heap? Se o ponteiro desconhecido fizer referência a uma instância na pilha, NUNCA DEVE SER APAGADA!

3) Você é 100% positivo, o ponteiro desconhecido é uma região de memory válida? Não, então, NUNCA DEVE SER DELITADO!

Ao todo, há muito pouco trabalho direto que pode ser feito usando um tipo de ponteiro desconhecido (void *). No entanto, indiretamente, o void * é um ótimo recurso para os desenvolvedores de C ++ confiarem quando a ambigüidade de dados é necessária.

Se você quer apenas um buffer, use malloc / free. Se você precisar usar new / delete, considere uma class de wrapper trivial:

 template struct size_buffer { char data_[ size_]; operator void*() { return (void*)&data_; } }; typedef sized_buffer<100> OpaqueBuffer; // logical description of your sized buffer OpaqueBuffer* ptr = new OpaqueBuffer(); delete ptr; 

Para o caso particular de char.

char é um tipo intrínseco que não possui um destruidor especial. Portanto, os argumentos dos vazamentos são discutidos.

sizeof (char) é geralmente um, então não há argumento de alinhamento. No caso de plataforma rara em que o tamanho de (char) não é um, eles alocam memory alinhada o suficiente para seu char. Portanto, o argumento de alinhamento também é discutível.

O malloc / free seria mais rápido neste caso. Mas você perde std :: bad_alloc e tem que verificar o resultado de malloc. Chamar os operadores globais de novos e excluir pode ser melhor, pois ignorar o intermediário.