Definindo a variável como NULL após a liberação

156

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 NULLnã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.

Alphaneo
fonte
3
é sempre útil poder verificar se ptr == NULLantes de fazer algo com ele. Se você não anular seus ponteiros livres, obteria, ptr != NULLmas ainda assim, um ponteiro inutilizável.
Ki Jéy
Os indicadores pendentes podem levar a vulnerabilidades exploráveis, como Use-After-Free .
precisa saber é o seguinte

Respostas:

285

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.

Martin v. Löwis
fonte
3
Não entendo por que você "inicializaria ponteiros para NULL antes que eles recebessem um valor verdadeiro de ponteiro"?
Paul Biggar
26
@ Paul: No caso específico, a declaração pode ser lida 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.
Martin v. Löwis 01/09/09
1
Pessoalmente, acho que, em qualquer base de código não trivial, obter um erro para remover a referência nula é tão vago quanto obter um erro para remover a referência de um endereço que você não possui. Eu pessoalmente nunca me incomodo.
precisa saber é o seguinte
9
Wilhelm, o ponto é que, com uma desreferência de ponteiro nulo, você obtém uma falha determinada e a localização real do problema. Um acesso incorreto pode ou não travar e corromper dados ou comportamento de maneiras inesperadas em locais inesperados.
Amit Naidu
4
Na verdade, inicializar o ponteiro para NULL tem pelo menos uma desvantagem significativa: ele pode impedir que o compilador avise sobre variáveis ​​não inicializadas. A menos que a lógica do seu código realmente lide explicitamente com esse valor para o ponteiro (por exemplo, se (nPtr == NULL) faz alguma coisa ...), é melhor deixá-lo como está.
Eric
37

Definir um ponteiro para NULLdepois freeé 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 NULLdepois freedeve evitar o temido problema de "dupla liberação" quando o mesmo valor de ponteiro é passado para freemais 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 para free. Escusado será dizer que definir um ponteiro para NULLdepois não freealcanç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 para freemais de uma vez. Nesses casos, definir o ponteiro NULLe 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 NULLou não do valor do ponteiro NULL, é perfeitamente adequado definir o valor do ponteiro para NULLdepois free. Mas, como regra geral de "boas práticas" (como em "sempre coloque o ponteiro para NULLdepois free"), é mais uma vez uma farsa bem conhecida e bastante inútil, frequentemente seguida por algumas por razões puramente religiosas e semelhantes ao vodu.

Formiga
fonte
1
Definitivamente. Não me lembro de ter causado um duplo-livre que seria corrigido definindo o ponteiro para NULL após a liberação, mas já causou muitos deles.
LnxPrgr3
4
@ Ant "duvidoso" é um pouco demais. Tudo depende do caso de uso. Se o valor do ponteiro for usado em sentido verdadeiro / falso, não é apenas uma prática válida, é uma prática recomendada.
Coder
1
@ Codificador Completamente errado. Se o valor do ponteiro for usado em um verdadeiro sentido falso para saber se ele apontou ou não um objeto antes da chamada para liberar, não é apenas não uma prática recomendada, está errado . Por exemplo: foo* bar=getFoo(); /*more_code*/ free(bar); /*more_code*/ return bar != NULL;. Aqui, definindo bara NULLapós a chamada para freefará a função de pensar que nunca mais tinha um bar e retornar o valor errado!
David Schwartz
Eu não acho que o principal benefício seja proteger contra um duplo grátis, mas sim capturar indicadores pendentes mais cedo e com mais segurança. Por exemplo, ao liberar uma estrutura que contém recursos, ponteiros para a memória alocada, identificadores de arquivo, etc., à medida que libero os ponteiros da memória contida e fecho os arquivos contidos, Nulo os respectivos membros. Então, se um dos recursos for acessado por meio de um ponteiro pendente por engano, o programa tende a apresentar falhas sempre, sempre. Caso contrário, sem o NULLing, os dados liberados ainda não poderão ser substituídos e o erro poderá não ser facilmente reproduzível.
jimhark
1
Concordo que o código bem estruturado não deve permitir o caso em que um ponteiro é acessado após ser liberado ou o caso em que ele é liberado duas vezes. Mas, no mundo real, meu código será modificado e / ou mantido por alguém que provavelmente não me conhece e não tem tempo e / ou habilidades para fazer as coisas corretamente (porque o prazo é sempre ontem). Portanto, costumo escrever funções à prova de balas que não causam pane no sistema, mesmo que sejam mal utilizadas.
mfloris 26/03/19
35

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.

Mike McNertney
fonte
1
+1 Este é realmente um ponto muito bom. Não é o raciocínio sobre "liberdade dupla" (que é completamente falso), mas isso . Não sou fã de NULL-ing mecânico de ponteiros depois free, mas isso realmente faz sentido.
AnT
Se você pudesse acessar um ponteiro depois de liberá-lo pelo mesmo ponteiro, é ainda mais provável que você acessasse um ponteiro depois de liberar o objeto apontado por outro ponteiro. Portanto, isso não ajuda em nada - você ainda precisa usar outro mecanismo para garantir que não acesse um objeto através de um ponteiro depois de liberá-lo por outro. Você também pode usar esse método para proteger no mesmo estojo de ponteiro.
David Schwartz
1
@DavidSchwartz: Não concordo com o seu comentário. Quando tive que escrever uma pilha para um exercício universitário há algumas semanas, tive um problema, investiguei algumas horas. Acessei alguma memória já liberada em algum momento (a liberação era algumas linhas muito cedo). E às vezes isso leva a um comportamento muito estranho. Se eu tivesse definido o ponteiro como NULL após liberá-lo, haveria um segfault "simples" e eu teria economizado algumas horas de trabalho. Então, +1 para esta resposta!
mozzbozz
2
@katze_sonne Até um relógio parado está certo duas vezes por dia. É muito mais provável que a configuração de ponteiros para NULL oculte bugs, impedindo que acessos errôneos a objetos já liberados causem falhas no código que verifica NULL e silenciosamente falha ao verificar um objeto que deveria ter verificado. (Talvez definir ponteiros para NULL após a liberação em compilações de depuração específicas possa ser útil, ou configurá-los para um valor diferente de NULL que é garantido para falha de segundo pode fazer sentido. Mas esse absurdo aconteceu para ajudá-lo uma vez não é um argumento a seu favor .)
David Schwartz
@DavidSchwartz Bem, isso parece razoável ... Obrigado pelo seu comentário, vou considerar isso no futuro! :) +1
mozzbozz
20

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:

#if DEBUG_VERSION
void myfree(void **ptr)
{
    free(*ptr);
    *ptr = NULL;
}
#else
#define myfree(p) do { void ** p_tmp = (p); free(*(p_tmp)); *(p_tmp) = NULL; } while (0)
#endif

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.

deslumbrado
fonte
3
A versão macro possui um bug sutil se você o usar após uma instrução if sem colchetes.
Mark Ransom
O que há com o (vazio) 0? Este código faz: if (x) myfree (& x); mais do_foo (); torna-se if (x) {free (* (& x)); * (& x) = nulo; } vazio 0; mais do_foo (); O resto é um erro.
21139 jmucchiello
Essa macro é um local perfeito para o operador de vírgula: livre ( (p)), * (p) = nulo. Obviamente, o próximo problema é avaliar * (p) duas vezes. Deve ser {void * _pp = (p); livre (* _ pp); * _pp = nulo; } O pré-processador não é divertido.
jmucchiello
5
A macro não deve estar entre colchetes, deve estar em um do { } while(0)bloco para que if(x) myfree(x); else dostuff();não quebre.
22711 Chris Lutz
3
Como Lutz disse, o corpo macro 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.
Mike Clark
7

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.

Tadeusz A. Kadłubowski
fonte
5
O programa nem sempre trava com um segfault. Se a maneira como você acessa o ponteiro significa que um deslocamento grande o suficiente é aplicado a ele antes da referência, ele pode alcançar a memória endereçável: ((MyHugeStruct *) 0) -> fieldNearTheEnd. E isso é antes mesmo de você lidar com hardware que não falha automaticamente no acesso 0. No entanto, é mais provável que o programa trate com um segfault.
Steve Jessop
7

Definir o ponteiro para a freememó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 nPtrestá fora do escopo logo depois nPtr = NULL, não parece haver um motivo para defini-lo NULL. No entanto, no caso de um structmembro 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 NULLdepois de cada livre, mas tem o potencial de apontar grandes problemas.

Jared Oberhaus
fonte
7

o erro mais comum em c é o duplo grátis. Basicamente, você faz algo assim

free(foobar);
/* lot of code */
free(foobar);

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ória

if(foobar != null){
  free(foobar);
}

Observe 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.

RageZ
fonte
11
Você pode realmente chamar free () sem marcar - free (NULL) é definido como não fazer nada.
1040 Amber
5
Isso não esconde bugs? (Como liberando demais.)
Georg Schölly
1
thanx, eu entendi. Eu tentei: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 }
Shaobo Wang
5
Como eu disse, 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 exigiria free(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 ponteiros NULL, isso não deve ser imposto a eles.
22720 Chris Lutz
2
Existem poucas coisas no mundo que revelam a falta de profissionalismo de parte do autor do código C. Mas eles incluem "verificar o ponteiro quanto a NULL antes de chamar 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 com sizeof").
AnT
6

A idéia por trás disso é parar a reutilização acidental do ponteiro liberado.

Mitch Wheat
fonte
4

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.

Steven Canfield
fonte
No Windows, pelo menos, as compilações de depuração definirão a memória como 0xdddddddd; portanto, quando você usa um ponteiro para excluir a memória que você conhece imediatamente. Deve haver mecanismos semelhantes em todas as plataformas.
i_am_jorf
2
jeffamaphone, o bloco de memória excluído pode ter sido realocado e atribuído a outro objeto quando você usar o ponteiro novamente.
Constantin
4

No padrão ANSI C:

void free(void *ptr);

A função livre faz com que o espaço apontado por ptr seja desalocado, isto é, disponibilizado para alocação adicional. Se ptr for um ponteiro nulo, nenhuma ação ocorrerá. Caso contrário, se o argumento não corresponder a um ponteiro retornado anteriormente pela função calloc, malloc ou realloc, ou se o espaço tiver sido desalocado por uma chamada para free ou realloc, o comportamento será indefinido.

"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:

void safe_free(void** ptr)
{
  free(*ptr);
  *ptr = NULL;
}
Vijay Mathew
fonte
@DrPizza - Um erro (na minha opinião) é algo que faz com que o seu programa não funcione como deveria. Se uma ocultação dupla oculta interrompe seu programa, é um erro. Se funcionar exatamente como foi planejado, não será um erro.
22720 Chris Lutz
@DrPizza: Acabei de encontrar um argumento sobre o motivo de alguém configurá-lo NULLpara evitar erros de mascaramento. stackoverflow.com/questions/1025589/… Parece que em ambos os casos alguns erros são ocultados.
Georg Schölly
1
Esteja ciente de que um ponteiro para ponteiro vazio tem seus problemas: c-faq.com/ptrs/genericpp.html
Secure
3
@ Chris, não, a melhor abordagem é a estrutura do código. Não jogue mallocs aleatórios e libere toda a sua base de código, mantenha coisas relacionadas juntas. O "módulo" que aloca um recurso (memória, arquivo, ...) é responsável por liberá-lo e deve fornecer uma função para isso, que também cuida dos ponteiros. Para qualquer recurso específico, você tem exatamente um local onde está alocado e um local onde é liberado, ambos próximos.
Secure
4
@ Chris Lutz: Hogwash. Se você escrever um código que libera o mesmo ponteiro duas vezes, seu programa terá um erro lógico. Mascarar esse erro lógico e não travar não significa que o programa esteja correto: ele ainda está fazendo algo sem sentido. Não há cenário em que justificar a escrita de um duplo livre.
DrPizza
4

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.

jcoder
fonte
4

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

free(ptr);
ptr = NULL;
Jalindar
fonte
3

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.

i_am_jorf
fonte
2

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:


if(ptr)
   ptr->CallSomeMethod();

Marcar explicitamente o ponteiro como NULL após liberá-lo permite esse tipo de uso em C / C ++.

Aamir
fonte
5
Em muitos casos, onde um ponteiro NULL não faz sentido, seria preferível escrever uma afirmação.
Erich Kitzmueller
2

Isso pode ser mais um argumento para inicializar todos os ponteiros para NULL, mas algo assim pode ser um bug muito sorrateiro:

void other_func() {
  int *p; // forgot to initialize
  // some unrelated mallocs and stuff
  // ...
  if (p) {
    *p = 1; // hm...
  }
}

void caller() {
  some_func();
  other_func();
}

pacaba no mesmo lugar na pilha que o anterior nPtr, portanto ainda pode conter um ponteiro aparentemente válido. Atribuir a *ppode 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 ...

sth
fonte
2

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

pierrotlefou
fonte
2

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.

dente afiado
fonte
2

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.

Jaap Weel
fonte
2

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.

Projeto de lei IV
fonte
Boa resposta. Eu gostaria de adicionar uma coisa. É bom fazer uma revisão do banco de dados de erros por vários motivos. Mas, no contexto da pergunta original, lembre-se de que seria difícil saber quantos problemas inválidos de ponteiro foram impedidos ou, pelo menos, detectados tão cedo que não chegaram ao banco de dados de bugs. O histórico de erros fornece melhores evidências para adicionar regras de codificação.
jimhark
2

Existem dois motivos:

Evite falhas ao liberar duas vezes

Escrito por RageZ em uma pergunta duplicada .

O erro mais comum em c é o duplo grátis. Basicamente, você faz algo assim

free(foobar);
/* lot of code */
free(foobar);

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ória

if(foobar != NULL){
  free(foobar);
}

Observe 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 por não terem esse problema e também por não terem que deixar para desenvolver o gerenciamento de memória como um todo.

Evite usar ponteiros já liberados

Escrito por Martin v. Löwis em outra resposta .

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.

Georg Schölly
fonte
1

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 diz tst.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.

Jens
fonte
1

É sempre aconselhável declarar uma variável de ponteiro com NULL , como,

int *ptr = NULL;

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:

free(ptr);
ptr = NULL;

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

*ptr = 20; //Points to 0x1000
free(ptr);
int *q = (int *)malloc(sizeof(int) * 2); //Points to 0x1000
*ptr = 30; //Since ptr and q are pointing to the same address, so the value of the address to which q is pointing would also change.
Pankaj Kumar Thapa
fonte
1

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.

Ehsan
fonte