Pergunta

Na minha empresa, há uma regra de codificação que diz, depois de afastada qualquer memória, redefinir 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, a definição de NULL não tem qualquer significado. Ou estou faltando alguma coisa?

Se não há sentido em tais casos, eu vou levá-la até com a "equipe de qualidade" para remover esta regra de codificação. Por favor conselho.

Foi útil?

Solução

Configuração ponteiros não utilizados para NULL é um estilo defensivo, protegendo contra pendurado erros de ponteiro. Se um apontador pendente é acessado depois de ser libertado, você pode ler ou sobrescrever a memória aleatória. Se um ponteiro nulo é acessado, você tem um acidente de imediato na maioria dos sistemas, dizendo-lhe de imediato qual é o erro.

Para variáveis ??locais, pode ser um pouco inútil pouco se é "óbvio" que o ponteiro não é acessado mais depois de ser libertado, assim que este estilo é mais adequado para os dados dos membros e variáveis ??globais. Mesmo para as variáveis ??locais, pode ser uma boa abordagem se a função continua depois que a memória é liberada.

Para completar o estilo, você também deve inicializar os ponteiros para NULL antes que eles se atribuído um valor de ponteiro verdade.

Outras dicas

A definição de um ponteiro para NULL após free é uma prática duvidosa que muitas vezes é popularizado como uma regra "boa programação" em um patentemente falsa premissa. É uma daquelas falsas verdades que pertencem aos "sons certo" categoria, mas na realidade conseguir absolutamente nada de útil (e, por vezes, leva a consequências negativas).

Alegadamente, definindo um ponteiro para NULL após free é suposto para evitar que o "double free" problema temido quando o mesmo valor de ponteiro é passado para free mais de uma vez. Na realidade, porém, em 9 casos em cada 10 o "double free" verdadeiro problema ocorre quando diferente ponteiro objetos mantendo o mesmo valor de ponteiro são usados ??como argumentos para free. Escusado será dizer que, estabelecendo um ponteiro para NULL após free consegue absolutamente nada para evitar o problema em tais casos.

É claro que é possível correr em problema "double free" quando se utiliza o mesmo objeto ponteiro como um argumento para free. No entanto, em situações de realidade, como que normalmente indica um problema com a estrutura lógica geral do código, não um mero acidental "double free". A maneira correta de lidar com o problema em tais casos é rever e repensar a estrutura do código, a fim de evitar a situação quando o mesmo ponteiro é passado para free mais de uma vez. Em tais casos, ajuste o ponteiro para NULL e considerando o problema "fixo" é nada mais do que uma tentativa de varrer o problema para debaixo do tapete. Ele simplesmente não vai funcionar no caso geral, porque o problema com a estrutura do código vai sempre encontrar uma outra maneira de se manifestar.

Finalmente, se o seu código é projetado especificamente para contar com o valor do ponteiro ser NULL ou não NULL, é perfeitamente bem para definir o valor ponteiro para NULL após free. Mas, como regra geral "boa prática" (como em "sempre definir o ponteiro para NULL após free") é, mais uma vez, um bem conhecido e bastante inútil falso, muitas vezes seguido por alguns por puramente religiosa, razões voodoo-like .

A maioria das respostas têm-se centrado na prevenção de uma dupla graça, mas definir o ponteiro para NULL tem outro benefício. Depois de libertar um ponteiro, que a memória está disponível para ser realocado por outra chamada para malloc. Se você ainda tiver o ponteiro originais em torno de você pode acabar com um bug onde você tentar usar o ponteiro após livre e corrupto alguma outra variável, e, em seguida, seu programa entra num estado desconhecido e todos os tipos de coisas ruins podem acontecer (falhar se você 're sorte, corrupção de dados se você não está com sorte). Se você tivesse definir o ponteiro para NULL após livre, qualquer tentativa de leitura / gravação através desse ponteiro mais tarde resultaria em um segfault, que é geralmente preferível à corrupção de memória aleatório.

Por ambas as razões, pode ser uma boa idéia para definir o ponteiro para NULL após free (). Nem sempre é necessário, no entanto. Por exemplo, se a variável ponteiro sai do escopo imediatamente após free (), não há muita razão para configurá-lo para NULL.

Esta é considerada uma boa prática para evitar a substituição de memória. Na função acima, não é necessário, mas muitas vezes quando ele é feito, pode encontrar erros de aplicação.

Tente algo parecido com isto em vez disso:

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

O DEBUG_VERSION permite perfil libera na depuração de código, mas ambos são funcionalmente o mesmo.

Editar :. Adicionado do ... while como sugerido abaixo, graças

Se você chegar ponteiro que tem sido free () d, pode quebrar ou não. Que a memória pode ser realocados para outra parte do seu programa e então você começa a corrupção de memória,

Se você definir o ponteiro para NULL, em seguida, se você acessá-lo, o programa sempre falhas com um segfault. Não mais ,, às vezes funciona '', não mais ,, cai no caminho unpredictible ''. É a maneira mais fácil de depurar.

Definir o ponteiro para os meios de memória free'd que qualquer tentativa de acesso que a memória através do ponteiro irá falhar imediatamente, em vez de causar um comportamento indefinido. Isso torna muito mais fácil para determinar onde as coisas deram errado.

Eu posso ver o seu argumento: desde nPtr vai fora do escopo logo após nPtr = NULL, não parece ser uma razão para configurá-lo para NULL. No entanto, no caso de um membro struct ou em outro lugar onde o ponteiro não está indo imediatamente fora do escopo, faz mais sentido. Não é imediatamente claro se ou não esse ponteiro será usado novamente pelo código que não deve usá-lo.

É provável que a regra é afirmado sem fazer uma distinção entre estes dois casos, porque é muito mais difícil de aplicar automaticamente a regra, e muito menos para os desenvolvedores para segui-lo. Não faz mal aos ponteiros de ajuste com NULL após cada livre, mas tem o potencial de apontar grandes problemas.

o erro mais comum em c é o dobro livre. Basicamente você fazer algo assim

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

e acabam muito ruim, a tentativa OS para libertar alguma memória já libertou e geralmente segfault. Assim, a boa prática é definir a NULL, assim você pode fazer teste e verifique se você realmente precisa para libertar esta memória

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

também de notar que free(NULL) não vai fazer nada para que você não tem que escrever a instrução if. Eu não sou realmente um guru OS mas estou muito mesmo agora a maioria dos sistemas operacionais deixaria de funcionar em duplo livre.

Essa também é uma razão principal pela qual todas as línguas com coleta de lixo (Java, .Net) estava tão orgulhoso de não ter este problema e também não ter que sair para os desenvolvedores o gerenciamento de memória como um todo.

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

Este (CAN) ser realmente importante. Embora você liberar a memória, uma parte posterior do programa pôde alocar algo novo que acontece a terra no espaço. O ponteiro de idade seria apontam agora para uma parte válida da memória. Em seguida, é possível que alguém iria usar o ponteiro, resultando em estado de programa inválido.

Se você NULL o ponteiro, então qualquer tentativa de usá-lo vai 0x0 dereference e falhar ali, que é fácil de depurar. ponteiros aleatórios que apontam para memória aleatório é difícil de depurar. É, obviamente, não é necessário, mas, em seguida, é por isso que ele está em um documento de boas práticas.

A partir do padrão ANSI C:

void free(void *ptr);

A função livre faz com que o espaço apontado por ptr a ser desalocada, isto é, disponibilizados para posterior alocação. Se ptr é um ponteiro nulo, nenhuma ação ocorre. Caso contrário, se o argumento não corresponde um ponteiro anteriormente retornado pelo calloc, malloc, ou realloc função, ou se o espaço foi desalocada por um chamar para libertar ou realloc, o comportamento é indefinido.

"o comportamento indefinido" é quase sempre uma falha de programa. De modo a evitar este é seguro para repor o ponteiro para NULL. free () em si não pode fazer isso, pois é só passou um ponteiro, não um ponteiro para um ponteiro. Você também pode escrever uma versão mais segura da livre () que NULLs o ponteiro:

void safe_free(void** ptr)
{
  free(*ptr);
  *ptr = NULL;
}

Eu acho que isso seja pequena ajuda como na minha experiência, quando as pessoas aceder a uma alocação de memória liberada é quase sempre porque eles têm um outro ponteiro para ele em algum lugar. E então ele entra em conflito com outro padrão de codificação pessoal que é "evitar a desordem inútil", então eu não fazê-lo como eu acho que raramente ajuda e torna o código um pouco menos legível.

No entanto - não vou definir a variável como nulo se o ponteiro não é suposto ser usado novamente, mas muitas vezes o design de nível superior dá-me uma razão para defini-lo como nulo qualquer maneira. Por exemplo, se o ponteiro é um membro de uma classe e eu tiver excluído o que aponta para, em seguida, o "contrato" se você gosta da classe é que esse membro irá apontar para algo válido a qualquer momento por isso deve ser definido como nulo por essa razão. Uma pequena distinção, mas eu acho que um passo importante.

Em C ++ é importante sempre estar pensando que é dono esses dados quando você alocar parte da memória (a menos que você estiver usando ponteiros inteligentes, mas, mesmo assim, é necessária alguma reflexão). E esse processo tende a levar a ponteiros em geral, sendo um membro de alguma classe e, geralmente, você quer uma classe para estar em um estado válido em todos os momentos, ea maneira mais fácil de fazer isso é para definir a variável de membro para NULL para indicar que pontos a nada agora.

Um padrão comum é definir todos os ponteiros membros para NULL no construtor e têm a chamada destructor excluir em qualquer ponteiros para dados que o projeto diz que a classe é dono . Claramente, neste caso, você tem que definir o ponteiro para NULL quando você excluir alguma coisa para indicar que você não possui quaisquer dados antes.

Assim, para resumir, sim, eu muitas vezes definir o ponteiro para NULL após a exclusão de alguma coisa, mas é como parte de um projeto maior e pensamentos sobre quem possui os dados em vez de devido a seguir cegamente uma regra padrão de codificação. Eu não fazê-lo no seu exemplo como eu acho que não há nenhum benefício para fazê-lo e acrescenta "desordem" que na minha experiência é tão responsável por erros e código ruim como este tipo de coisa.

Recentemente me deparo com a mesma pergunta depois que eu estava procurando a resposta. I chegou a esta conclusão:

É melhor prática, e é preciso seguir esta para torná-lo portátil em todos os sistemas (incorporados).

free() é uma função da biblioteca, que varia como uma mudança da plataforma, para que você não deve esperar que depois de passar ponteiro para essa função e depois de libertar memória, este ponteiro será definido como NULL. Isto pode não ser o caso de alguma biblioteca implementada para a plataforma.

então sempre ir para

free(ptr);
ptr = NULL;

Esta regra é útil quando você está tentando evitar os seguintes cenários:

1) Você tem uma função muito longo com a lógica complicada e gerenciamento de memória e você não quiser reutilizar acidentalmente o ponteiro para a memória apagado depois na função.

2) O ponteiro é uma variável de membro de uma classe que tem um comportamento bastante complexo e você não quiser reutilizar acidentalmente o ponteiro para a memória eliminado em outras funções.

No seu cenário, não faz muito sentido, mas se a função fosse para obter mais tempo, poderia importa.

Você pode argumentar que defini-lo como NULL pode realmente mascarar erros de lógica mais tarde, ou no caso em que você assumir que é válido, você ainda falhar em NULL, por isso não importa.

Em geral, gostaria de aconselhá-lo a configurá-lo para NULL quando você pensa que é uma boa idéia, e não se preocupar quando você pensa que não vale a pena. Foco em vez de escrever funções curtas e classes bem desenhados.

Para adicionar ao que outros já 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();

Explicitamente marcando o ponteiro como NULL após o esvaziamento permite que para este tipo de utilização em C / C ++.

Isto pode ser mais um argumento para inicializar todos os ponteiros para NULL, mas algo como isso pode ser um erro 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();
}

p acaba no mesmo lugar na pilha como o ex-nPtr, por isso ainda pode conter um ponteiro aparentemente válido. Atribuindo a *p pode substituir todos os tipos de coisas não relacionadas e levar a erros feios. Especialmente se o compilador inicializa variáveis ??locais com zero no modo de depuração, mas não uma vez otimizações estão ligados. Assim, a depuração cria não mostram quaisquer sinais do bug enquanto compilações explodir aleatoriamente ...

Defina o ponteiro que acaba de ser liberado para NULL não é obrigatório, mas uma boa prática. Desta forma, você pode evitar 1) usando um libertou pontas 2) libertá-la towice

Configurações de um ponteiro para NULL é proteger agains chamados double-free -. Uma situação quando free () é chamado mais de uma vez para o mesmo endereço, sem realocar o bloco nesse endereço

leads Double-livres a um comportamento indefinido - corrupção normalmente pilha ou imediatamente bater o programa. Chamando free () para um ponteiro NULL não faz nada e, portanto, é garantido para ser seguro.

Portanto, a melhor prática a menos que você agora para garantir que o escopo folhas ponteiro imediatamente ou logo após free () é para definir esse ponteiro para NULL modo que mesmo se free () é chamado novamente, que agora é chamado para um ponteiro NULL e comportamento indefinido é contornado.

A idéia é que se você tentar cancelar o ponteiro não são mais válidas após libertá-la, você quer falhar rígido (segfault) em vez de silenciosamente e misteriosamente.

Mas ... cuidado. Nem todos os sistemas causar um segfault se você excluir a referência NULL. (Pelo menos algumas versões) AIX, * (int *) 0 == 0, e Solaris tem compatibilidade opcional com este AIX "recurso".

Para a pergunta original: Definir o ponteiro para NULL directamente após libertar o conteúdo é um completo desperdício de tempo, desde que o código cumpre todos os requisitos, está totalmente depurado e nunca será modificado novamente. Por outro lado, na defesa anulando um ponteiro que foi libertado pode ser bastante útil quando alguém impensadamente acrescenta um novo bloco de código sob o free (), quando o projeto do módulo original não é correta, e no caso dele -compiles-mas-não-fazer-que-eu-quero bugs.

Em qualquer sistema, não é uma meta inalcançável de torná-lo mais fácil da coisa certa, eo custo irredutível de medições imprecisas. Em C estamos oferecido um conjunto de ferramentas muito afiadas, muito fortes, que podem criar muitas coisas nas mãos de um trabalhador qualificado, e infligem todos os tipos de lesões metafóricos quando manuseado de forma inadequada. Alguns são difíceis de compreender ou usar corretamente. E as pessoas, sendo naturalmente avessos ao risco, fazer coisas irracionais como a verificação de um ponteiro para o valor NULL antes de chamar livre com ele ...

O problema da medida é que sempre que você tentativa de dividir o bem do menos bom, quanto mais complexo o caso, o mais provável que você obter uma medição ambígua. Se o objetivo é fazer manter apenas boas práticas, em seguida, alguns mais ambíguas se jogou para fora com o realmente não é bom. Se seu objetivo é eliminar a não é bom, então as ambiguidades podem ficar com o bem. Os dois objetivos, manter apenas bom ou eliminar claramente ruim, parece ser diametralmente opostas, mas geralmente há um terceiro grupo que não é nem uma coisa nem outra, um pouco dos dois.

Antes de fazer um caso com o departamento de qualidade, tente olhar através da base de dados bug para ver quantas vezes, se alguma vez, valores de ponteiro inválido causado problemas que tiveram de ser escrito. Se você quiser fazer diferença real, identificar o problema mais comum em seu código de produção e propor três maneiras de evitá-lo

Há duas razões:

evitar acidentes quando duplo libertação

Escrito por RageZ em uma duplicado questão .

O bug mais comum em c é o dobro livre. Basicamente você fazer algo como que

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

e acabam muito ruim, a tentativa OS para libertar alguma memória já libertados e geralmente ele segfault. Assim, a boa prática é conjunto para NULL, para que pode fazer teste e verifique se você realmente necessidade de libertar esta memória

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

também de notar que free(NULL) não vai fazer nada para que você não tem que escrever a instrução if. eu não sou realmente um guru OS mas estou bastante mesmo agora a maioria dos sistemas operacionais iria falhar em dupla livre.

Isso também é uma razão principal pela qual todos línguas com coleta de lixo (Java, .Net) estava tão orgulhoso de não tendo esse problema e também não ter que sair para o desenvolvedor do gerenciamento de memória como um todo.

Evite o uso de ponteiros já libertados

Escrito por Martin v Löwis em uma another resposta .

Configuração ponteiros não utilizados para NULL é um estilo defensivo, contra proteger pendurado erros de ponteiro. Se um dangling ponteiro é acedida depois de ser libertado, você pode ler ou substituir aleatório memória. Se um ponteiro nulo é acessado, você tem um acidente de imediato sobre mais sistemas, dizendo-lhe imediatamente o que é o erro.

Para variáveis ??locais, pode ser um pouco inútil se é "Óbvio" que o ponteiro não é acessada mais depois de ser libertado, por isso, este estilo é mais apropriado para dados membro e variáveis ??globais. Até para as variáveis ??locais, pode ser uma boa aproximar se a função continua depois que a memória é liberada.

Para completar o estilo, você também deve inicializar os ponteiros para NULL antes eles se atribuído um ponteiro verdade valor.

Como você tem uma equipe de garantia de qualidade no lugar, deixe-me acrescentar um ponto menor sobre QA. Algumas ferramentas de controle de qualidade automatizados para C atribuições bandeira vontade para ponteiros libertados como "atribuição inútil ptr". Por exemplo PC-lint / FlexeLint de Gimpel Software diz tst.c 8 Warning 438: Last value assigned to variable 'nPtr' (defined at line 5) not used

Existem maneiras de mensagens seletivamente suprimir, assim você ainda pode satisfazer tanto os requisitos de QA, deve sua equipe decidir-lo.

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

int *ptr = NULL;

Vamos digamos, ptr está apontando para 0x1000 endereço de memória. Depois de usar free(ptr), é sempre aconselhável para anular a variável ponteiro, declarando novamente para NULL . por exemplo:.

free(ptr);
ptr = NULL;

Se não re-declarado NULL , a variável ponteiro ainda continua a apontar para o mesmo endereço ( 0x1000 ), esta variável ponteiro é chamado de pendurados ponteiro . Se você definir outra variável ponteiro (digamos, q ) e alocar dinamicamente o endereço para o novo ponteiro, há uma chance de tomar o mesmo endereço ( 0x1000 ) pelo novo ponteiro variável. Se no caso, você usa o mesmo ponteiro ( ptr ) e atualizar o valor no endereço apontado pelo mesmo ponteiro ( ptr ), então o programa vai acabar escrevendo um valor para o lugar onde q está apontando (desde p e q estã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.

Para encurtar a história: Você não quer acidentalmente (por engano) acesso o endereço que você tenha libertado. Porque, quando você liberar o endereço, você permite que o endereço na pilha a atribuir a alguma outra aplicação.

No entanto, se você não definir o ponteiro para NULL, e por engano try to-referência de ponteiro, ou alterar o valor desse endereço; VOCÊ PODE ainda fazê-lo. Mas não é algo que você iria logicamente quer fazer.

Por que ainda posso acessar o local de memória que eu tenho liberado? Porque: Você pode ter liberar a memória, mas a variável ponteiro ainda tinha informações sobre o endereço de memória heap. Assim, como uma estratégia defensiva, por favor, defina-o como NULL.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top