Pergunta

Muitas vezes vejo verificação de código legado para NULL antes de excluir um ponteiro, semelhante a,

if (NULL != pSomeObject) 
{
    delete pSomeObject;
    pSomeObject = NULL;
}

Existe alguma razão para a verificação de um ponteiro NULL antes de excluí-lo? Qual é a razão para definir o ponteiro para NULL depois?

Foi útil?

Solução

É perfeitamente "segura" para apagar um ponteiro nulo; isso equivale efetivamente a um não-op.

A razão que você pode querer verificar para nulo antes de excluir é que tentar excluir um ponteiro nulo pode indicar um bug em seu programa.

Outras dicas

A C ++ garantias padrão que é legal usar um ponteiro nulo em um Excluir-expressão (§8.5.2.5 / 2). No entanto, é não especificado se isso vai chamar uma função deallocation (operator delete ou operator delete[]; §8.5.2.5 / 7, nota).

Se uma função deallocation padrão (ou seja fornecido pela biblioteca padrão) é chamado com um ponteiro nulo, então a chamada não tem efeito (§6.6.4.4.2 / 3).

Mas não é especificado o que acontece se a função deallocation não é fornecido pela biblioteca padrão -. Ou seja, o que acontece quando sobrecarregar operator delete (ou operator delete[])

Um programador competente iria lidar com ponteiros nulos em conformidade dentro a função de desalocação, mais do que antes da chamada, como mostrado na code.Likewise do OP, definindo o ponteiro para nullptr / NULL após a exclusão só serve muito propósito limitado. Algumas pessoas gostam de fazer isso no espírito do defensiva programação: ele vai fazer o comportamento do programa um pouco mais previsíveis no caso de um erro: acessando o ponteiro após eliminação resultará em um acesso de ponteiro nulo ao invés de um acesso a um local de memória aleatório. Embora ambas as operações são um comportamento indefinido, o comportamento de um acesso ponteiro nulo é muito mais previsível na prática (na maioria das vezes resulta em uma queda direta ao invés de corrupção de memória). Desde corrupções de memória são especialmente difíceis de depurar, redefinir ponteiros excluídos ajudas de depuração.

- Claro que isso é tratar o sintoma e não a causa (ou seja, o bug). Você deve tratar a redefinição ponteiros como cheiro de código. Limpo, código moderno C ++ fará propriedade memória clara e estaticamente verificado (usando ponteiros inteligentes ou mecanismos equivalentes), e, assim, comprovadamente evitar esta situação.

Bonus: Uma explicação de operator delete sobrecarregado:

operator delete é (apesar do nome) uma função que pode ser sobrecarregado como qualquer outra função. Esta função é chamada internamente para cada chamada de operator delete com argumentos correspondentes. O mesmo é verdadeiro para operator new.

A sobrecarga operator new (e depois também operator delete) faz sentido em algumas situações quando você deseja controlar com precisão como a memória é alocada. Fazer isso não é ainda muito difícil, mas algumas precauções devem ser tomadas para assegurar o comportamento correto. Scott Meyers descreve isso em grande detalhe Effective C ++ .

Por enquanto, vamos apenas dizer que queremos sobrecarregar a versão global operator new para depuração. Antes de fazermos isso, um curto prazo sobre o que acontece no código a seguir:

klass* pobj = new klass;
// … use pobj.
delete pobj;

O que realmente acontece aqui? Bem acima pode ser traduzido aproximadamente com o seguinte código:

// 1st step: allocate memory
klass* pobj = static_cast<klass*>(operator new(sizeof(klass)));
// 2nd step: construct object in that memory, using placement new:
new (pobj) klass();

// … use pobj.

// 3rd step: call destructor on pobj:
pobj->~klass();
// 4th step: free memory
operator delete(pobj);

Observe a etapa 2, onde chamamos new com uma sintaxe um pouco estranho. Esta é uma chamada para a chamada colocação new que tem um endereço de e constrói um objeto naquele endereço. Este operador pode ser sobrecarregado também. Neste caso, ele só serve para chamar o construtor da klass classe.

Agora, sem mais delongas, aqui está o código para uma versão sobrecarregada dos operadores:

void* operator new(size_t size) {
    // See Effective C++, Item 8 for an explanation.
    if (size == 0)
        size = 1;

    cerr << "Allocating " << size << " bytes of memory:";

    while (true) {
        void* ret = custom_malloc(size);

        if (ret != 0) {
            cerr << " @ " << ret << endl;
            return ret;
        }

        // Retrieve and call new handler, if available.
        new_handler handler = set_new_handler(0);
        set_new_handler(handler);

        if (handler == 0)
            throw bad_alloc();
        else
            (*handler)();
    }
}

void operator delete(void* p) {
    cerr << "Freeing pointer @ " << p << "." << endl;
    custom_free(p);
}

Este código só usa uma implementação personalizada do malloc / free internamente, como fazem a maioria das implementações. Ele também cria uma saída de depuração. Considere o seguinte código:

int main() {
    int* pi = new int(42);
    cout << *pi << endl;
    delete pi;
}

Ele produziu o seguinte resultado:

Allocating 4 bytes of memory: @ 0x100160
42
Freeing pointer @ 0x100160.

Agora, este código faz algo fundamentalmente diferente do que a implementação padrão de operator delete: Não fiz teste para ponteiros nulos O compilador não verificar isso para que os compila o código acima, mas pode dar! erros desagradáveis ??no tempo de execução quando você tentarponteiros nulos exclusão.

No entanto, como eu disse antes, este comportamento é realmente inesperado e um escritor biblioteca deve cuidar para verificar se há ponteiros nulos na operator delete. Esta versão é muito melhor:

void operator delete(void* p) {
    if (p == 0) return;
    cerr << "Freeing pointer @ " << p << "." << endl;
    free(p);
}

Em conclusão, embora a implementação malfeita de operator delete podem exigir verificações nulos explícitas no código do cliente, este é um comportamento não-padrão e só deve ser tolerado em suporte legado ( se em tudo ).

cheques de exclusão para NULL internamente. Seu teste é redundante

Apagar nulo é um não-op. Não há nenhuma razão para verificar se nulo antes de chamar delete.

Você pode querer verificar para nulo por outras razões se o ponteiro sendo nula carrega algumas informações adicionais que se preocupam.

De acordo com a C ++ 03 5.3.5 / 2, é seguro excluir um ponteiro nulo. Este seguinte é citado a partir do padrão:

Em qualquer alternativa, se o valor do operando de exclusão é a ponteiro nulo a operação não tem efeito.

Se pSomeObject é NULL, exclusão não vai fazer nada. Então, não, você não tem que verificar para NULL.

Consideramos que é uma boa prática para NULL atribuir ao ponteiro depois de excluí-lo se for de todo possível que algum idiota pode tentar usar o ponteiro. Usando um ponteiro NULL é ligeiramente melhor do que usar um ponteiro para quem sabe o que (o ponteiro NULL irá causar um acidente, o ponteiro para a memória excluídos podem não)

Não há nenhuma razão para verificar NULL antes de excluir. Atribuindo NULL após a exclusão pode ser necessário se em algum lugar o código verifica são feitas se algum objeto já está alocado através da realização de uma verificação de NULL. Um exemplo seria algum tipo de dados em cache que está alocado na demanda. Sempre que você limpar o cache-objeto que você NULL atribuir ao ponteiro de modo que o código que aloca o objeto sabe que ele precisa para executar uma atribuição.

Eu acredito que o desenvolvedor anterior codificado-lo "redundante" para salvar alguns milissegundos: É uma boa coisa para ter o ponteiro ser definido como NULL ao ser eliminado, assim você pode usar uma linha como a seguinte logo após excluir o objeto:

if(pSomeObject1!=NULL) pSomeObject1=NULL;

Mas então de exclusão está fazendo essa comparação exata de qualquer maneira (não fazer nada se é NULL). Por que fazer isso duas vezes? Você pode pSomeObject atribuir sempre para NULL depois de chamar de exclusão, independentemente de seu valor atual -. Mas isso seria um pouco redundante, se tivesse esse valor já

Assim, a minha aposta é o autor dessas linhas tentou garantir pSomeObject1 seria sempre NULL depois de ser eliminado, sem incorrer no custo de um teste potencialmente desnecessária e atribuição.

Depende do que você está fazendo. Algumas implementações mais antigas do free, por exemplo, não será feliz se eles são passados ??um ponteiro NULL. Algumas bibliotecas ainda tem esse problema. Por exemplo, XFree na biblioteca Xlib diz:

Descrição

A função XFree é um De propósito geral Xlib rotina que liberta os dados especificados. Você deve usá-lo para liberar quaisquer objetos que estavam alocada por Xlib, a menos que uma alternativa função é especificado explicitamente o objeto. Um ponteiro nulo não pode ser passados ??para essa função.

Por isso, considero liberando ponteiros NULL como um bug e você estará seguro.

Quanto às minhas observações, a exclusão de um ponteiro nulo usando exclusão é seguro em máquinas baseadas unix ike PARISC e Itanium. Mas é bastante inseguro para sistemas Linux como o processo iria falhar então.

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