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 para char*
antes da exclusão?
c++
memory-management
casting
void-pointers
An̲̳̳drew
fonte
fonte
Respostas:
Depende de "seguro". Geralmente funciona porque as informações são armazenadas junto com o ponteiro sobre a alocação em si, de modo que o desalocador pode retorná-lo ao lugar certo. Nesse sentido, é "seguro", desde que seu alocador use tags de limite interno. (Muitos fazem.)
No entanto, conforme mencionado em outras respostas, excluir um ponteiro void não chamará destruidores, o que pode ser um problema. Nesse sentido, não é "seguro".
Não há nenhuma boa razão para fazer o que você está fazendo da maneira que está fazendo. Se você deseja escrever suas próprias funções de desalocação, pode usar modelos de função para gerar funções com o tipo correto. Um bom motivo para fazer isso é gerar alocadores de pool, que podem ser extremamente eficientes para tipos específicos.
Conforme mencionado em outras respostas, esse é um comportamento indefinido em C ++. Em geral, é bom evitar comportamentos indefinidos, embora o assunto em si seja complexo e repleto de opiniões conflitantes.
fonte
sizeof(T*) == sizeof(U*)
para todosT,U
sugere que deve ser possível ter umavoid *
implementação de coletor de lixo não baseada em modelo . Mas então, quando o gc realmente tem que deletar / liberar um ponteiro exatamente esta questão surge. Para fazê-lo funcionar, você precisa de wrappers destruidores de função lambda (urgh) ou precisa de algum tipo de coisa dinâmica de "tipo como dados" que permite ir e vir entre um tipo e algo armazenável.A exclusão por meio de um ponteiro nulo não é definida pelo padrão C ++ - consulte a seção 5.3.5 / 3:
E sua nota de rodapé:
.
fonte
NULL
alguma diferença para gerenciamento de memória de aplicativo?Não é uma boa ideia e não é algo que você faria em C ++. Você está perdendo suas informações de tipo sem motivo.
Seu destruidor não será chamado nos objetos em seu array que você está excluindo ao chamá-lo para tipos não primitivos.
Em vez disso, você deve substituir new / delete.
Excluir o vazio * provavelmente irá liberar sua memória corretamente por acaso, mas é errado porque os resultados são indefinidos.
Se por algum motivo desconhecido para mim você precisar armazenar seu ponteiro em um vazio * e então liberá-lo, você deve usar malloc e free.
fonte
Excluir um ponteiro vazio é perigoso porque os destruidores não serão chamados no valor para o qual ele realmente aponta. Isso pode resultar em vazamentos de memória / recursos em seu aplicativo.
fonte
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 objeto que foi alocado dinamicamente. Faça isso, você forma uma expressão de exclusão com um ponteiro para esse objeto . Você nunca "apaga um ponteiro". O que você realmente faz é "deletar um objeto que é identificado por seu endereço".Agora vemos por que a pergunta não faz sentido: um ponteiro vazio não é o "endereço de um objeto". É apenas um endereço, sem semântica. Ele pode ter vindo do endereço de um objeto real, mas que a informação é perdida, porque foi codificada no tipo do ponteiro originais. A única maneira de restaurar um ponteiro de objeto é lançar o ponteiro void de volta para um ponteiro de objeto (o que requer que o autor saiba o que o ponteiro significa).
void
em si é um tipo incompleto e, portanto, nunca o tipo de um objeto, e um ponteiro vazio nunca pode ser usado para identificar um objeto. (Os objetos são identificados em conjunto por seu tipo e endereço.)fonte
delete
pode ser um valor de ponteiro nulo, um ponteiro para um objeto não array criado por uma nova expressão anterior ou um ponteiro para um subobjeto que representa uma classe base de tal objeto. Caso contrário, o comportamento é indefinido. " Então, se um compilador aceita seu código sem um diagnóstico, não é nada além de um bug no compilador ...delete void_pointer
. É um comportamento indefinido. Os programadores nunca devem invocar um comportamento indefinido, mesmo que a resposta pareça fazer o que o programador deseja que seja feito.Se você realmente deve fazer isso, por que não cortar o homem médio (os
new
edelete
operadores) e chamar o mundialoperator new
eoperator delete
diretamente? (Claro, se você está tentando instrumentar os operadoresnew
edelete
, na verdade deve reimplementaroperator new
eoperator delete
.)Observe que
malloc()
, ao contrário ,operator new
gerastd::bad_alloc
falha (ou chama onew_handler
se um estiver registrado).fonte
Porque char não tem nenhuma lógica destruidora especial. ISTO não funcionará.
O d'ctor não será chamado.
fonte
Se você deseja usar void *, por que não usa apenas malloc / free? new / delete é mais do que apenas gerenciamento de memória. Basicamente, new / delete chama um construtor / destruidor e há mais coisas acontecendo. Se você apenas usar tipos integrados (como char *) e excluí-los por meio de void *, funcionaria, mas ainda não é recomendado. O resultado final é usar malloc / free se quiser usar void *. Caso contrário, você pode usar funções de modelo para sua conveniência.
fonte
Muita gente já comentou dizendo que não, não é seguro deletar um void pointer. Eu concordo com isso, mas também gostaria de acrescentar que se você estiver trabalhando com ponteiros nulos para alocar matrizes contíguas ou algo semelhante, você pode fazer isso
new
para poder usardelete
com segurança (com, ahem , um pouco de trabalho extra). Isso é feito alocando um ponteiro vazio para a região de memória (chamado de 'arena') e, em seguida, fornecendo o ponteiro para a nova arena. Consulte esta seção nas Perguntas frequentes sobre C ++ . Esta é uma abordagem comum para implementar pools de memória em C ++.fonte
Dificilmente há uma razão para fazer isso.
Em primeiro lugar, se você não sabe o tipo dos dados, e tudo o que sabe é que são
void*
, então você realmente deveria tratar esses dados como um blob sem tipo de dados binários (unsigned char*
) e usarmalloc
/free
para lidar com eles . Isso é necessário às vezes para coisas como dados de forma de onda e similares, onde você precisa passarvoid*
ponteiros para C apis. Isso é bom.Se você fazer saber o tipo de dados (ou seja, tem um ctor / dtor), mas por algum motivo você acabou com um
void*
ponteiro (por qualquer motivo que você tem) , então você realmente deve lançá-lo de volta para o tipo de conhecê-lo para seja , e convoquedelete
-o.fonte
Usei void *, (também conhecido como tipos desconhecidos) em minha estrutura durante a reflexão de código e outros feitos de ambigüidade e, até agora, não tive problemas (vazamento de memória, violações de acesso, etc.) de nenhum compilador. Apenas avisos devido à operação não ser padrão.
Faz sentido excluir um desconhecido (vazio *). Certifique-se de que o ponteiro segue estas diretrizes ou pode deixar de fazer sentido:
1) O ponteiro desconhecido não deve apontar para um tipo que possui um desconstrutor trivial e, portanto, quando lançado como um ponteiro desconhecido, NUNCA DEVE SER EXCLUÍDO. Exclua o ponteiro desconhecido APÓS colocá-lo de volta no tipo ORIGINAL.
2) A instância está sendo referenciada como um ponteiro desconhecido na memória limite da pilha ou limite da pilha? Se o ponteiro desconhecido fizer referência a uma instância na pilha, ele NUNCA DEVE SER EXCLUÍDO!
3) Você tem 100% de certeza de que o ponteiro desconhecido é uma região de memória válida? Não, então NUNCA DEVE SER DELIMITADO!
Ao todo, há muito pouco trabalho direto que pode ser feito usando um tipo de ponteiro desconhecido (void *). No entanto, indiretamente, o vazio * é um grande recurso para os desenvolvedores C ++ confiarem quando a ambigüidade dos dados é necessária.
fonte
Se você quiser apenas um buffer, use malloc / free. Se você deve usar new / delete, considere uma classe de wrapper trivial:
fonte
Para o caso particular de char.
char é um tipo intrínseco que não possui um destruidor especial. Portanto, os argumentos de vazamento são discutíveis.
sizeof (char) é geralmente um, então também não há argumento de alinhamento. No caso de uma plataforma rara onde o sizeof (char) não é um, eles alocam memória alinhada o suficiente para seu char. Portanto, o argumento do alinhamento também é discutível.
malloc / free seria mais rápido neste caso. Mas você perde std :: bad_alloc e tem que verificar o resultado de malloc. Chamar os novos operadores globais e excluir pode ser melhor, pois ignora o intermediário.
fonte
new
realmente está definido para jogar. Isso não é verdade. É compilador e dependente da chave do compilador. Consulte por exemplo as/GX[-] enable C++ EH (same as /EHsc)
chaves MSVC2019 . Também em sistemas embarcados, muitos optam por não pagar taxas de desempenho por exceções C ++. Portanto, a frase que começa com "Mas você perdeu std :: bad_alloc ..." é questionável.