O que significa fazer uma “ verificação de nulo ” em C ou C ++?

Tenho aprendido C ++ e estou tendo dificuldade em entender null. Em particular, os tutoriais que li mencionam fazer uma “verificação de nulos”, mas não tenho certeza do que isso significa ou por que é necessário.

  • O que exatamente é nulo?
  • O que significa “verificar se há nulo”?
  • Sempre preciso verificar se há nulo?

Qualquer exemplo de código seria muito apreciado.

Comentários

Resposta

Em C e C ++, os ponteiros são inerentemente inseguros, ou seja, quando você cancela a referência de um ponteiro, é sua própria responsabilidade certificar-se de que aponta para algum lugar válido; isso faz parte do “gerenciamento manual de memória” (em oposição aos esquemas de gerenciamento automático de memória implementados em linguagens como Java , PHP ou o tempo de execução .NET, que não permitirá que você crie referências inválidas sem um esforço considerável).

Uma solução comum que detecta muitos erros é definir todos os ponteiros que não apontam para nada como NULL (ou, em C ++ correto, 0) e verificando isso antes de acessar o ponteiro. Especificamente, é prática comum inicializar todos os ponteiros para NULL (a menos que você já tenha algo para apontá-los ao declará-los) e defini-los como NULL quando delete ou free() eles (a menos que saiam do escopo imediatamente depois disso). Exemplo (em C, mas também em C ++ válido):

void fill_foo(int* foo) { *foo = 23; // this will crash and burn if foo is NULL } 

Uma versão melhor:

void fill_foo(int* foo) { if (!foo) { // this is the NULL check printf("This is wrong\n"); return; } *foo = 23; } 

Sem a verificação de nulo, passar um ponteiro NULL para esta função causará um segfault, e não há nada que você possa fazer – o sistema operacional simplesmente encerrará seu processo e talvez despeje o núcleo ou exiba uma caixa de diálogo de relatório de falha. Com a verificação de nulos em vigor, você pode executar o tratamento de erros adequado e recuperar-se sem problemas – corrija o problema você mesmo, aborte a operação atual, escreva uma entrada de log, notifique o usuário, o que for apropriado.

Comentários

  • @MrLister o que você quer dizer com cheques nulos não ‘ não funcionam em C ++? Você só precisa inicializar o ponteiro para nulo ao declará-lo.
  • O que quero dizer é que você deve se lembrar de definir o ponteiro como NULL ou ele ganhará ‘ t trabalhar. E se você se lembrar, em outras palavras, se você sabe que o ponteiro é NULL, você não ‘ terá a necessidade de chamar fill_foo de qualquer maneira. fill_foo verifica se o ponteiro tem um valor, não se o ponteiro tem um valor válido . Em C ++, não é garantido que os ponteiros sejam NULL ou tenham um valor válido.
  • Um assert () seria uma solução melhor aqui. Não ‘ não faz sentido tentar ” estar seguro “. Se NULL foi passado, ‘ está obviamente errado, então por que não travar explicitamente para deixar o programador totalmente ciente? (E na produção, não ‘ importa, porque você ‘ provou que ninguém ligará para fill_foo () com NULL, certo? Realmente, ‘ não é tão difícil.)
  • Não ‘ se esqueça para mencionar que uma versão ainda melhor desta função deve usar referências em vez de ponteiros, tornando a verificação NULL obsoleta.
  • Não é disso que se trata o gerenciamento manual de memória, e um programa gerenciado também explodirá ( ou levantar uma exceção pelo menos, assim como um programa nativo fará na maioria das linguagens,) se você tentar cancelar a referência de uma referência nula.

Resposta

As outras respostas cobriram praticamente sua pergunta exata. Uma verificação de nulo é feita para ter certeza de que o ponteiro que você recebeu realmente aponta para uma instância válida de um tipo (objetos, primitivos, etc).

Vou adicionar meu próprio conselho aqui, entretanto. Evite verificações nulas. 🙂 Verificações nulas (e outras formas de Programação defensiva) desordenam o código e, na verdade, o tornam mais sujeito a erros do que outras técnicas de tratamento de erros.

Minha técnica favorita quando se trata de ponteiros de objeto é usar o padrão de objeto nulo . Isso significa retornar um (ponteiro – ou melhor ainda, referência a uma) matriz ou lista vazia em vez de nula, ou retornando uma string vazia (“”) em vez de nula, ou mesmo a string “0” (ou algo equivalente a “nada” no contexto) onde você espera que seja analisado para um inteiro.

Como um bônus, aqui está “algo que você talvez não saiba sobre o ponteiro nulo, que foi (primeiro formalmente) implementado por CAR Hoare para a linguagem Algol W em 1965.

Chamo isso de meu erro de um bilhão de dólares. Foi a invenção da referência nula em 1965. Naquela época, eu estava projetando o primeiro sistema de tipos abrangente para referências em um objeto linguagem orientada (ALGOL W). Meu objetivo era garantir que todo uso de referências fosse absolutamente seguro, com checagem realizada automaticamente pelo compilador. Mas não resisti à tentação de colocar uma referência nula, simplesmente porque era assim fácil de implementar. Isso levou a inúmeros erros, vulnerabilidades e travamentos do sistema, que provavelmente causaram um bilhão de dólares de dor e danos nos últimos quarenta anos.

Comentários

  • Objeto nulo é ainda pior do que apenas ter um ponteiro nulo. Se um algoritmo X requer dados Y que você não possui, então isso é um bug em seu programa , que você está simplesmente escondendo ao fingir que tem.
  • Depende de o contexto e o teste de qualquer forma para ” presença de dados ” supera o teste de null em meu livro. Pela minha experiência, se um algoritmo funciona em, digamos, uma lista, e a lista está vazia, então o algoritmo simplesmente não tem nada a fazer e faz isso apenas usando instruções de controle padrão, como for / foreach.
  • Se o algoritmo não tem nada a ver, então por que você o está chamando? E a razão pela qual você pode querer chamá-lo em primeiro lugar é porque faz algo importante .
  • @DeadMG Porque os programas têm a ver com entrada e, no mundo real, ao contrário tarefas de casa, a entrada pode ser irrelevante (por exemplo, vazio). O código ainda é chamado de qualquer maneira. Você tem duas opções: ou você verifica a relevância (ou vazio), ou você projeta seus algoritmos para que eles leiam e funcionem bem sem verificar explicitamente a relevância usando declarações condicionais.
  • Vim aqui para fazer quase o mesmo comentário, então lhe dei meu voto. No entanto, eu também acrescentaria que isso é representativo de um problema maior de objetos zumbis – sempre que houver objetos com inicialização (ou destruição) em vários estágios que não estão totalmente vivos, mas não totalmente mortos. Quando você vê o código ” seguro ” em linguagens sem finalização determinística que adicionou verificações em cada função para ver se o objeto foi descartado, é este problema geral que o cria ‘ s cabeça. Você nunca deve if-null, você deve trabalhar com estados que têm os objetos de que precisam durante sua vida.

Resposta

O valor do ponteiro nulo representa um “lugar nenhum” bem definido; é um valor de ponteiro inválido que tem a garantia de ser diferente de qualquer outro valor de ponteiro. Tentar cancelar a referência de um ponteiro nulo resulta em um comportamento indefinido e geralmente leva a um erro de tempo de execução, portanto, você deseja ter certeza de que um ponteiro não é NULL antes de tentar removê-lo. Várias funções de biblioteca C e C ++ retornarão um ponteiro nulo para indicar uma condição de erro. Por exemplo, a função de biblioteca malloc retornará um valor de ponteiro nulo se não puder alocar o número de bytes que foram solicitados, e tentar acessar a memória por meio desse ponteiro (geralmente) levará para um erro de tempo de execução:

int *p = malloc(sizeof *p * N); p[0] = ...; // this will (usually) blow up if malloc returned NULL 

Portanto, precisamos ter certeza de que a chamada malloc foi bem-sucedida verificando o valor de p contra NULL:

int *p = malloc(sizeof *p * N); if (p != NULL) // or just if (p) p[0] = ...; 

Agora, segure suas meias um minuto, isso vai ter um bit bumpy.

Há um ponteiro nulo valor e um ponteiro nulo constante , e os dois não são necessariamente os mesmos. O ponteiro nulo valor é qualquer valor que a arquitetura subjacente usa para representar “lugar nenhum”. Este valor pode ser 0x00000000, ou 0xFFFFFFFF, ou 0xDEADBEEF ou algo completamente diferente. Não assuma que o valor do ponteiro nulo é sempre 0.

O ponteiro nulo constante , OTOH, é sempre uma expressão integral com valor 0. No que diz respeito ao seu código-fonte , 0 (ou qualquer expressão integral avaliada como 0) representa um ponteiro nulo. C e C ++ definem a macro NULL como a constante de ponteiro nulo. Quando seu código é compilado, o ponteiro nulo constante será substituído pelo ponteiro nulo apropriado valor no código de máquina gerado.

Além disso, esteja ciente de que NULL é apenas um dos muitos valores possíveis do ponteiro inválido ; se você declarar uma variável de ponteiro automático sem inicializá-la explicitamente, como

int *p; 

o valor inicialmente armazenado na variável é indeterminado , e pode não corresponder a um endereço de memória válido ou acessível. Infelizmente, não há uma maneira (portátil) de saber se um valor de ponteiro não NULL é válido ou não antes de tentar usá-lo. Portanto, se você estiver lidando com ponteiros, geralmente é uma boa ideia inicializá-los explicitamente para NULL quando você os declara e para defini-los como NULL quando não estão apontando ativamente para nada.

Observe que este é mais um problema em C do que em C ++; C ++ idiomático não deve usar ponteiros tanto.

Resposta

Existem alguns métodos, todos essencialmente fazem o mesmo coisa.

 int *foo = NULL; //sometimes set to 0x00 or 0 or 0L instead of NULL 

verificação de nulo (verifique se o ponteiro é nulo), versão A

 if( foo == NULL) 

verificação nula, versão B

 if( !foo ) //since NULL is defined as 0, !foo will return a value from a null pointer 

verificação nula, versão C

 if( foo == 0 ) 

Dos três, prefiro usar a primeira verificação, pois ela informa explicitamente aos futuros desenvolvedores o que você estava tentando verificar E deixa claro que você esperava que foo fosse um ponteiro.

Resposta

Você não. A única razão para usar um ponteiro em C ++ é porque você deseja explicitamente a presença de ponteiros nulos; caso contrário, você pode usar uma referência, que é semanticamente mais fácil de usar e garante não nula.

Comentários

  • @James: ‘ novo ‘ no modo kernel?
  • @James: Uma implementação de C ++ que representa os recursos que uma maioria significativa de C ++ os codificadores gostam. Isso inclui todos os recursos da linguagem C ++ 03 (exceto export) e todos os recursos da biblioteca C ++ 03 e TR1 e um bom pedaço de C ++ 11.
  • Eu gostaria que as pessoas não ‘ dissessem isso ” as referências garantem não nulas. ” Elas não ‘ t. É tão fácil gerar uma referência nula quanto um ponteiro nulo, e eles se propagam da mesma maneira.
  • @Stargazer: A questão é 100% redundante quando você apenas usa as ferramentas da maneira que os designers de linguagem e bons a prática sugere que você deveria.
  • @DeadMG, não ‘ importa se é redundante. Você não ‘ não respondeu à pergunta . Eu ‘ direi novamente: -1.

Resposta

Se você não verificar o valor NULL, especialmente se for um ponteiro para uma estrutura, talvez você encontre uma vulnerabilidade de segurança – desreferenciação do ponteiro NULL. A desreferenciação do ponteiro NULL pode levar a outras vulnerabilidades de segurança sérias, como estouro de buffer, condição de corrida … que pode permitir que o invasor controle o seu computador.

Muitos fornecedores de software como Microsoft, Oracle, Adobe, Apple … lançam patch de software para corrigir essas vulnerabilidades de segurança. Acho que você deveria verifique o valor NULL de cada ponteiro 🙂

Deixe uma resposta

O seu endereço de email não será publicado. Campos obrigatórios marcados com *