Na minha empresa, existe uma regra de codificação que diz que, após liberar qualquer memória, redefina a variável para NULL
. Por exemplo ...
void some_func ()
{
int *nPtr;
nPtr = malloc (100);
free (nPtr);
nPtr = NULL;
return;
}
Eu sinto que, em casos como o código mostrado acima, definir como NULL
não tem nenhum significado. Ou eu estou esquecendo de alguma coisa?
Se não houver sentido nesses casos, vou abordar a "equipe de qualidade" para remover esta regra de codificação. Conselho por favor.
c
coding-style
malloc
free
heap-memory
Alphaneo
fonte
fonte
ptr == NULL
antes de fazer algo com ele. Se você não anular seus ponteiros livres, obteria,ptr != NULL
mas ainda assim, um ponteiro inutilizável.Respostas:
Definir ponteiros não utilizados como NULL é um estilo defensivo, protegendo contra erros de ponteiros pendentes. Se um ponteiro danificado for acessado após ser liberado, você poderá ler ou substituir a memória aleatória. Se um ponteiro nulo for acessado, você terá uma falha imediata na maioria dos sistemas, informando imediatamente qual é o erro.
Para variáveis locais, pode ser um pouco inútil se for "óbvio" que o ponteiro não seja mais acessado após ser liberado, portanto esse estilo é mais apropriado para dados de membros e variáveis globais. Mesmo para variáveis locais, pode ser uma boa abordagem se a função continuar após a liberação da memória.
Para concluir o estilo, você também deve inicializar os ponteiros para NULL antes que eles recebam um valor verdadeiro de ponteiro.
fonte
int *nPtr=NULL;
. Agora, eu concordo que isso seria redundante, com um malloc seguindo na próxima linha. No entanto, se houver código entre a declaração e a primeira inicialização, alguém poderá começar a usar a variável, mesmo que ela ainda não tenha valor. Se você inicializar nulo, receberá o segfault; sem, você pode novamente ler ou gravar memória aleatória. Da mesma forma, se a variável posteriormente for inicializada apenas condicionalmente, os acessos defeituosos posteriores deverão causar falhas instantâneas se você se lembrar de inicializar nulo.Definir um ponteiro para
NULL
depoisfree
é uma prática duvidosa que costuma ser popularizada como uma regra de "boa programação" em uma premissa claramente falsa. É uma daquelas verdades falsas que pertencem à categoria "parece certo", mas, na realidade, não alcançam absolutamente nada de útil (e às vezes leva a consequências negativas).Alegadamente, definir um ponteiro para
NULL
depoisfree
deve evitar o temido problema de "dupla liberação" quando o mesmo valor de ponteiro é passado parafree
mais de uma vez. Na realidade, porém, em 9 casos em 10, o problema real de "dupla liberação" ocorre quando diferentes objetos de ponteiro com o mesmo valor de ponteiro são usados como argumentos parafree
. Escusado será dizer que definir um ponteiro paraNULL
depois nãofree
alcança absolutamente nada para evitar o problema nesses casos.Obviamente, é possível encontrar um problema de "liberdade dupla" ao usar o mesmo objeto ponteiro que um argumento para
free
. No entanto, na realidade, situações como essa normalmente indicam um problema com a estrutura lógica geral do código, não um mero "duplo livre" acidental. Uma maneira adequada de lidar com o problema nesses casos é revisar e repensar a estrutura do código para evitar a situação em que o mesmo ponteiro é passado parafree
mais de uma vez. Nesses casos, definir o ponteiroNULL
e considerar o problema "corrigido" nada mais é do que uma tentativa de varrer o problema para debaixo do tapete. Simplesmente não funcionará no caso geral, porque o problema com a estrutura de código sempre encontrará outra maneira de se manifestar.Por fim, se o seu código foi projetado especificamente para depender
NULL
ou não do valor do ponteiroNULL
, é perfeitamente adequado definir o valor do ponteiro paraNULL
depoisfree
. Mas, como regra geral de "boas práticas" (como em "sempre coloque o ponteiro paraNULL
depoisfree
"), é mais uma vez uma farsa bem conhecida e bastante inútil, frequentemente seguida por algumas por razões puramente religiosas e semelhantes ao vodu.fonte
foo* bar=getFoo(); /*more_code*/ free(bar); /*more_code*/ return bar != NULL;
. Aqui, definindobar
aNULL
após a chamada parafree
fará a função de pensar que nunca mais tinha um bar e retornar o valor errado!A maioria das respostas concentrou-se em impedir a liberação dupla, mas definir o ponteiro para NULL tem outro benefício. Depois de liberar um ponteiro, essa memória fica disponível para ser realocada por outra chamada ao malloc. Se você ainda tiver o ponteiro original ao seu redor, poderá acabar com um erro no qual tentar usar o ponteiro após libertar e corromper alguma outra variável e, em seguida, seu programa entrar em um estado desconhecido e todos os tipos de coisas ruins podem acontecer (travar se você tiver sorte, corrupção de dados, se você não tiver sorte). Se você definisse o ponteiro como NULL após a liberação, qualquer tentativa de leitura / gravação desse ponteiro posteriormente resultaria em um segfault, que geralmente é preferível à corrupção aleatória da memória.
Por ambos os motivos, pode ser uma boa ideia definir o ponteiro para NULL após free (). Nem sempre é necessário, no entanto. Por exemplo, se a variável ponteiro sair do escopo imediatamente após free (), não há muito motivo para defini-la como NULL.
fonte
free
, mas isso realmente faz sentido.Isso é considerado uma boa prática para evitar a substituição da memória. Na função acima, é desnecessário, mas, muitas vezes, quando é feito, pode encontrar erros de aplicativo.
Tente algo assim:
O DEBUG_VERSION permite que o perfil seja liberado no código de depuração, mas ambos são funcionalmente iguais.
Edit : Added do ... while como sugerido abaixo, obrigado.
fonte
do { } while(0)
bloco para queif(x) myfree(x); else dostuff();
não quebre.do {X} while (0)
é IMO a melhor maneira de criar um corpo macro que "pareça e funcione como" uma função. A maioria dos compiladores otimiza o loop de qualquer maneira.Se você encontrar um ponteiro que está livre () d, ele pode quebrar ou não. Essa memória pode ser realocada para outra parte do seu programa e, em seguida, você recebe corrupção de memória,
Se você definir o ponteiro como NULL, se você acessá-lo, o programa sempre trava com um segfault. Não mais, às vezes funciona '', não mais, trava de maneira imprevisível ''. É muito mais fácil depurar.
fonte
Definir o ponteiro para a
free
memória significa que qualquer tentativa de acessar essa memória através do ponteiro falhará imediatamente, em vez de causar um comportamento indefinido. Torna muito mais fácil determinar onde as coisas deram errado.Percebo o seu argumento: como
nPtr
está fora do escopo logo depoisnPtr = NULL
, não parece haver um motivo para defini-loNULL
. No entanto, no caso de umstruct
membro ou de algum outro lugar em que o ponteiro não saia imediatamente do escopo, isso faz mais sentido. Não é imediatamente aparente se o ponteiro será ou não usado novamente por código que não deveria estar sendo usado.É provável que a regra seja declarada sem fazer distinção entre esses dois casos, porque é muito mais difícil impor a regra automaticamente, muito menos para os desenvolvedores a seguirem. Não faz mal definir indicadores
NULL
depois de cada livre, mas tem o potencial de apontar grandes problemas.fonte
o erro mais comum em c é o duplo grátis. Basicamente, você faz algo assim
e acaba muito ruim, o sistema operacional tenta liberar um pouco de memória já liberada e geralmente falha. Portanto, a boa prática é definir como
NULL
, para que você possa fazer o teste e verificar se realmente precisa liberar essa memóriaObserve também que
free(NULL)
não fará nada para que você não precise escrever a instrução if. Na verdade, eu não sou um guru de SO, mas sou bonito mesmo agora, a maioria dos sistemas operacionais falharia em dobro grátis.Essa também é a principal razão pela qual todas as linguagens com coleta de lixo (Java, dotnet) estavam tão orgulhosas de não ter esse problema e também de deixar para os desenvolvedores o gerenciamento de memória como um todo.
fonte
p = (char *)malloc(.....); free(p); if(p!=null) //p!=null is true, p is not null although freed { free(p); //Note: checking doesnot prevent error here }
free(void *ptr)
não é possível alterar o valor do ponteiro que é passado. Ele pode alterar o conteúdo do ponteiro, os dados armazenados nesse endereço , mas não o endereço em si ou o valor do ponteiro . Isso exigiriafree(void **ptr)
(que aparentemente não é permitido pelo padrão) ou uma macro (que é permitida e perfeitamente portátil, mas as pessoas não gostam de macros). Além disso, C não é sobre conveniência, é sobre dar aos programadores o controle que eles querem. Se eles não desejam a sobrecarga adicional de definir ponteirosNULL
, isso não deve ser imposto a eles.free
" (junto com itens como "converter o resultado das funções de alocação de memória" ou "usar sem pensar os nomes dos tipos comsizeof
").A idéia por trás disso é parar a reutilização acidental do ponteiro liberado.
fonte
Isso pode ser realmente importante. Embora você libere a memória, uma parte posterior do programa pode alocar algo novo que acaba pousando no espaço. Seu ponteiro antigo agora apontaria para um pedaço válido de memória. É então possível que alguém usasse o ponteiro, resultando em um estado de programa inválido.
Se você anular o ponteiro, qualquer tentativa de usá-lo desreferirá 0x0 e travará, o que é fácil de depurar. Ponteiros aleatórios apontando para memória aleatória são difíceis de depurar. Obviamente, não é necessário, mas é por isso que está em um documento de práticas recomendadas.
fonte
No padrão ANSI C:
"o comportamento indefinido" é quase sempre uma falha no programa. Para evitar isso, é seguro redefinir o ponteiro para NULL. O próprio free () não pode fazer isso, pois é passado apenas um ponteiro, não um ponteiro para um ponteiro. Você também pode escrever uma versão mais segura do free () que anula o ponteiro:
fonte
NULL
para evitar erros de mascaramento. stackoverflow.com/questions/1025589/… Parece que em ambos os casos alguns erros são ocultados.Acho que isso é de pouca ajuda, como na minha experiência quando as pessoas acessam uma alocação de memória liberada, é quase sempre porque elas têm outro ponteiro para ela em algum lugar. E então entra em conflito com outro padrão de codificação pessoal que é "Evite a confusão inútil", então não o faço porque acho que raramente ajuda e torna o código um pouco menos legível.
No entanto - não definirei a variável como nula se o ponteiro não for usado novamente, mas muitas vezes o design de nível superior me dá um motivo para defini-la como nula de qualquer maneira. Por exemplo, se o ponteiro for membro de uma classe e eu tiver excluído o que ele aponta, o "contrato", se você gosta da classe, é que esse membro apontará para algo válido a qualquer momento, portanto, deve ser definido como nulo por essa razão. Uma pequena distinção, mas acho importante.
No c ++, é importante sempre pensar em quem é o proprietário desses dados quando você aloca alguma memória (a menos que você esteja usando ponteiros inteligentes, mas mesmo assim algum pensamento é necessário). E esse processo tende a levar os ponteiros geralmente a serem membros de alguma classe e geralmente você deseja que uma classe esteja sempre em um estado válido, e a maneira mais fácil de fazer isso é definir a variável de membro como NULL para indicar seus pontos para nada agora.
Um padrão comum é definir todos os ponteiros de membro como NULL no construtor e fazer com que a chamada de destruidor exclua em qualquer ponteiro os dados que o seu design diz que a classe possui . Claramente, nesse caso, você deve definir o ponteiro como NULL quando excluir algo para indicar que não possui dados antes.
Então, para resumir, sim, costumo definir o ponteiro como NULL depois de excluir alguma coisa, mas isso faz parte de um projeto maior e de pensamentos sobre quem possui os dados, em vez de seguir cegamente uma regra padrão de codificação. Eu não faria isso no seu exemplo, pois acho que não há benefício em fazê-lo e acrescenta "confusão", que na minha experiência é tão responsável por bugs e códigos ruins quanto esse tipo de coisa.
fonte
Recentemente, me deparei com a mesma pergunta depois de procurar a resposta. Cheguei a esta conclusão:
É uma prática recomendada, e é preciso seguir isso para torná-lo portátil em todos os sistemas (incorporados).
free()
é uma função de biblioteca, que varia conforme a plataforma é alterada; portanto, você não deve esperar que após passar o ponteiro para essa função e após liberar memória, esse ponteiro seja definido como NULL. Pode não ser o caso de algumas bibliotecas implementadas para a plataforma.então sempre vá para
fonte
Esta regra é útil quando você está tentando evitar os seguintes cenários:
1) Você tem uma função muito longa com gerenciamento de lógica e memória complicado e não deseja reutilizar acidentalmente o ponteiro para a memória excluída posteriormente na função.
2) O ponteiro é uma variável membro de uma classe que possui um comportamento bastante complexo e você não deseja reutilizar acidentalmente o ponteiro para excluir a memória em outras funções.
No seu cenário, não faz muito sentido, mas se a função demorar mais, isso pode importar.
Você pode argumentar que defini-lo como NULL pode mascarar erros lógicos mais tarde ou, no caso de você assumir que é válido, você ainda trava no NULL, então isso não importa.
Em geral, aconselho que você defina NULL quando achar que é uma boa ideia, e não se preocupe quando achar que não vale a pena. Concentre-se em escrever funções curtas e aulas bem projetadas.
fonte
Para adicionar ao que os outros disseram, um bom método de uso do ponteiro é sempre verificar se é um ponteiro válido ou não. Algo como:
Marcar explicitamente o ponteiro como NULL após liberá-lo permite esse tipo de uso em C / C ++.
fonte
Isso pode ser mais um argumento para inicializar todos os ponteiros para NULL, mas algo assim pode ser um bug muito sorrateiro:
p
acaba no mesmo lugar na pilha que o anteriornPtr
, portanto ainda pode conter um ponteiro aparentemente válido. Atribuir a*p
pode substituir todos os tipos de coisas não relacionadas e levar a erros feios. Especialmente se o compilador inicializar variáveis locais com zero no modo de depuração, mas não quando as otimizações estiverem ativadas. Portanto, as compilações de depuração não mostram nenhum sinal do bug enquanto as compilações de lançamento explodem aleatoriamente ...fonte
Definir o ponteiro que acabou de ser liberado como NULL não é obrigatório, mas é uma boa prática. Dessa forma, você pode evitar 1) usar um ponto livre 2) liberá-lo duas vezes
fonte
Definir um ponteiro para NULL é proteger novamente o chamado duplo-livre - uma situação em que free () é chamado mais de uma vez para o mesmo endereço sem realocar o bloco nesse endereço.
A liberação dupla leva a um comportamento indefinido - geralmente acumula corrupção ou falha imediata do programa. Chamar free () para um ponteiro NULL não faz nada e, portanto, é garantido que é seguro.
Portanto, a melhor prática, a menos que você agora tenha certeza de que o ponteiro deixa o escopo imediatamente ou logo após o free (), é configurá-lo como NULL, para que, mesmo que free () seja chamado novamente, agora seja chamado um ponteiro NULL e um comportamento indefinido. é evadido.
fonte
A idéia é que, se você tentar desreferenciar o ponteiro que não é mais válido depois de liberá-lo, deseja falhar com força (segfault) em vez de silenciosa e misteriosamente.
Mas tenha cuidado. Nem todos os sistemas causam um segfault se você derreferenciar NULL. No (pelo menos algumas versões do) AIX, * (int *) 0 == 0, e o Solaris tem compatibilidade opcional com esse "recurso" do AIX.
fonte
Para a pergunta original: Definir o ponteiro como NULL diretamente após liberar o conteúdo é uma completa perda de tempo, desde que o código atenda a todos os requisitos, seja totalmente depurado e nunca será modificado novamente. Por outro lado, anular defensivamente um ponteiro que foi liberado pode ser bastante útil quando alguém adiciona um novo bloco de código sob o free (), quando o design do módulo original não está correto e, no caso dele, sem pensar -compila-mas-não-faz-o-que-quero-erros.
Em qualquer sistema, existe um objetivo inatingível de facilitar a coisa certa e o custo irredutível de medições imprecisas. Em C, oferecemos um conjunto de ferramentas muito afiadas e muito fortes, que podem criar muitas coisas nas mãos de um trabalhador qualificado e infligir todo tipo de ferimentos metafóricos quando manuseados de maneira inadequada. Alguns são difíceis de entender ou usar corretamente. E as pessoas, sendo naturalmente avessas ao risco, fazem coisas irracionais como verificar um valor NULL em um ponteiro antes de chamar de graça ...
O problema da medição é que, sempre que você tenta dividir bom de menos bom, quanto mais complexo o caso, maior a probabilidade de você obter uma medida ambígua. Se o objetivo é manter apenas boas práticas, algumas ambíguas são descartadas com as realmente não boas. Se o seu objetivo é eliminar o que não é bom, as ambiguidades podem ficar com o que é bom. Os dois objetivos, manter apenas o bem ou eliminar claramente o mal, pareceriam ser diametralmente opostos, mas geralmente há um terceiro grupo que não é nem um nem o outro, alguns de ambos.
Antes de discutir com o departamento de qualidade, tente examinar o banco de dados de erros para ver com que frequência, se é que alguma vez, valores inválidos de ponteiro causavam problemas que precisavam ser anotados. Se você quiser fazer a diferença real, identifique o problema mais comum no seu código de produção e proponha três maneiras de evitá-lo.
fonte
Existem dois motivos:
Evite falhas ao liberar duas vezes
Escrito por RageZ em uma pergunta duplicada .
Evite usar ponteiros já liberados
Escrito por Martin v. Löwis em outra resposta .
fonte
Como você possui uma equipe de garantia de qualidade, deixe-me acrescentar um ponto menor sobre o controle de qualidade. Algumas ferramentas automatizadas de controle de qualidade para C sinalizam atribuições a ponteiros liberados como "atribuição inútil para
ptr
". Por exemplo, PC-lint / FlexeLint da Gimpel Software diztst.c 8 Warning 438: Last value assigned to variable 'nPtr' (defined at line 5) not used
Existem maneiras de suprimir seletivamente as mensagens, para que você ainda possa atender aos dois requisitos de controle de qualidade, caso sua equipe o decida.
fonte
É sempre aconselhável declarar uma variável de ponteiro com NULL , como,
Digamos, ptr está apontando para o endereço de memória 0x1000 . Após o uso
free(ptr)
, é sempre aconselhável anular a variável do ponteiro declarando novamente como NULL . por exemplo:Se não declarada novamente como NULL , a variável de ponteiro ainda continua apontando para o mesmo endereço ( 0x1000 ), essa variável de ponteiro é chamada de ponteiro danificado . Se você definir outra variável de ponteiro (digamos, q ) e alocar dinamicamente o endereço para o novo ponteiro, existe a chance de pegar o mesmo endereço ( 0x1000 ) pela nova variável de ponteiro. Se, no caso, você usar o mesmo ponteiro ( ptr ) e atualizar o valor no endereço apontado pelo mesmo ponteiro ( ptr ), o programa acabará escrevendo um valor no local para onde q está apontando (já que p e q são apontando para o mesmo endereço (0x1000 )).
por exemplo
fonte
Longa história: Você não deseja acessar acidentalmente (por engano) o endereço que liberou. Porque, ao liberar o endereço, você permite que o endereço no heap seja alocado para outro aplicativo.
No entanto, se você não definir o ponteiro como NULL e, por engano, tente desassociar o ponteiro ou alterar o valor desse endereço; Você ainda pode fazê-lo. Mas não algo que você gostaria de fazer logicamente.
Por que ainda posso acessar o local da memória que liberei? Porque: Você pode liberar a memória, mas a variável ponteiro ainda tinha informações sobre o endereço de memória da pilha. Portanto, como estratégia defensiva, defina-o como NULL.
fonte