Verificando o ponteiro NULL em C / C ++ [fechado]

153

Em uma revisão de código recente, um colaborador está tentando impor que todas as NULLverificações nos ponteiros sejam executadas da seguinte maneira:

int * some_ptr;
// ...
if (some_ptr == NULL)
{
    // Handle null-pointer error
}
else
{
    // Proceed
}

ao invés de

int * some_ptr;
// ...
if (some_ptr)
{
    // Proceed
}
else
{
    // Handle null-pointer error
}

Concordo que o caminho dele é um pouco mais claro no sentido de que está dizendo explicitamente "Certifique-se de que este ponteiro não seja NULL", mas eu diria que, dizendo que qualquer pessoa que esteja trabalhando nesse código entenderá que usar uma variável de ponteiro em um ifestá implicitamente procurando NULL. Também acho que o segundo método tem uma chance menor de introduzir um bug do ilk:

if (some_ptr = NULL)

que é apenas uma dor absoluta para encontrar e depurar.

Qual o caminho que você prefere e por quê?

Bryan Marble
fonte
32
Ter um estilo consistente às vezes é mais importante do que o estilo escolhido.
Mark Ransom
15
@ Mark: a consistência é sempre o fator mais importante do seu estilo.
Sri 29/09/2010
13
"Também sinto que o segundo método tem uma chance menor de introduzir um bug do ilk: if (some_ptr = NULL)" É por isso que algumas pessoas usam if (NULL == some_ptr), não podem atribuir a NULL para que você obtenha um erro de compilação.
user122302
14
a maioria dos compiladores atualmente alerta sobre atribuição em condicionais - infelizmente, muitos desenvolvedores ignoram os avisos do compilador.
Mike Ellery
10
Não concordo com a afirmação acima de que a consistência é o que importa. Isso não tem nada a ver com consistência; não é o tipo bom de qualquer maneira. É nessa área que devemos deixar os programadores expressarem seu próprio estilo. Se existe uma pessoa que não entende imediatamente nenhuma das formas, deve parar de ler o código imediatamente e considerar uma mudança de carreira. Vou dizer mais do que isso: se houver uma consistência cosmética que você não possa aplicar automaticamente com um script, remova-o. O custo de esgotar a moral humana é MUITO mais importante do que aquelas decisões estúpidas que as pessoas tomam em uma reunião.
#

Respostas:

203

Na minha experiência, os testes do formulário if (ptr)ou if (!ptr)são preferidos. Eles não dependem da definição do símbolo NULL. Eles não expõem a oportunidade para a atribuição acidental. E eles são claros e sucintos.

Edit: Como o SoapBox aponta em um comentário, eles são compatíveis com classes C ++, como auto_ptrobjetos que atuam como ponteiros e que fornecem uma conversão boolpara permitir exatamente esse idioma. Para esses objetos, uma comparação explícita NULLteria que invocar uma conversão em ponteiro, que pode ter outros efeitos colaterais semânticos ou ser mais cara do que a simples verificação de existência que a boolconversão implica.

Eu tenho uma preferência por código que diga o que significa sem texto desnecessário. if (ptr != NULL)tem o mesmo significado, if (ptr)mas à custa de especificidade redundante. A próxima coisa lógica é escrever if ((ptr != NULL) == TRUE)e, dessa maneira, está a loucura. A linguagem C é clara que um booleano testado por if, whileou similar, tem um significado específico de valor diferente de zero é verdadeiro e zero é falso. A redundância não a torna mais clara.

RBerteig
fonte
23
Além disso, eles são compatíveis com as classes de wrapper de ponteiro (shared_ptr, auto_ptr, scoped_tr, etc) que geralmente substituem o operador bool (ou safe_bool).
SoapBox
2
Estou votando isso como a "resposta" simplesmente devido à referência a auto_ptr adicionada à discussão. Após escrever a pergunta, fica claro que isso é realmente mais subjetivo do que qualquer outra coisa e provavelmente não é adequado para uma "resposta" de fluxo de pilha, pois qualquer uma dessas opções pode estar "correta", dependendo das circunstâncias.
Bryan Marble
2
@Bryan, eu respeito isso e, se uma resposta "melhor" aparecer, sinta-se à vontade para mover a marca de verificação conforme necessário. Quanto à subjetividade, sim, é subjetiva. Mas é pelo menos uma discussão subjetiva em que há questões objetivas que nem sempre são óbvias quando a pergunta é feita. A linha está embaçada. E subjetiva ... ;-)
RBerteig
1
Uma coisa importante a ser observada: até recentemente, eu era a favor de escrever "if (ptr)" em vez de "if (ptr! = NULL)" ou "if (ptr! = Nullptr)", pois é mais conciso, por isso o código mais legível. Mas acabei de descobrir que a comparação gera (resp.) Aviso / erro para comparação com NULL / nullptr em compiladores recentes. Portanto, se você deseja verificar um ponteiro, é melhor fazer "if (ptr! = NULL)" em vez de "if (ptr)". Se você erroneamente declarar ptr como int, a primeira verificação gerará um aviso / erro, enquanto a última será compilada sem nenhum aviso.
Arnaud #
1
@ David 天宇 Wong Não, não foi isso que quis dizer. O exemplo nessa página é a sequência de duas linhas em int y = *x; if (!x) {...}que o otimizador pode raciocinar que, uma vez que *xseria indefinido se xfor NULL, ele não deve ser NULL e, portanto, !xdeve ser FALSE. A expressão não!ptr é um comportamento indefinido. No exemplo, se o teste fosse feito antes de qualquer uso *x, o otimizador estaria errado ao eliminar o teste.
RBerteig
52

if (foo)é claro o suficiente. Use-o.

wilx
fonte
25

Vou começar com isso: a consistência é o rei, a decisão é menos importante do que a consistência em sua base de código.

Em C ++

NULL é definido como 0 ou 0L em C ++.

Se você leu A linguagem de programação C ++ que Bjarne Stroustrup sugere usar 0explicitamente para evitar a NULLmacro ao realizar tarefas , não tenho certeza se ele fez o mesmo com as comparações, já faz um tempo desde que li o livro, acho que ele fez if(some_ptr)sem uma comparação explícita, mas sou confuso nisso.

A razão para isso é que a NULLmacro é enganosa (como quase todas as macros). Na verdade 0, é literal, não um tipo exclusivo, como o nome sugere. Evitar macros é uma das diretrizes gerais em C ++. Por outro lado, 0parece um número inteiro e não é quando comparado ou atribuído a ponteiros. Pessoalmente, eu poderia ir de qualquer maneira, mas normalmente pulo a comparação explícita (embora algumas pessoas não gostem disso, provavelmente é por isso que você tem um colaborador sugerindo uma alteração).

Independentemente dos sentimentos pessoais, essa é basicamente uma escolha do menos mau, pois não existe um método correto.

Isso é claro e é um idioma comum e eu prefiro, não há chance de atribuir um valor acidentalmente durante a comparação e ele lê claramente:

if(some_ptr){;}

Isso fica claro se você souber que some_ptré um tipo de ponteiro, mas também pode parecer uma comparação inteira:

if(some_ptr != 0){;}

Isso é claro, em casos comuns, faz sentido ... Mas é uma abstração com vazamento, NULLna verdade é 0literal e pode acabar sendo mal utilizada facilmente:

if(some_ptr != NULL){;}

O C ++ 0x possui nullptr, que agora é o método preferido, pois é explícito e preciso, mas tenha cuidado com a atribuição acidental:

if(some_ptr != nullptr){;}

Até que você possa migrar para o C ++ 0x, eu diria que é uma perda de tempo se preocupar com qual desses métodos você usa, todos eles são insuficientes e é por isso que o nullptr foi inventado (junto com problemas genéricos de programação que surgiram com o encaminhamento perfeito .) O mais importante é manter a consistência.

Em C

C é um animal diferente.

Em C NULL pode ser definido como 0 ou como ((vazio *) 0), C99 permite constantes de ponteiro nulo definidas de implementação. Portanto, na verdade, tudo se resume à definição de NULL da implementação e você terá que inspecioná-la na sua biblioteca padrão.

As macros são muito comuns e, em geral, são muito usadas para compensar deficiências no suporte genérico à programação na linguagem e em outras coisas. A linguagem é muito mais simples e a dependência do pré-processador é mais comum.

Nessa perspectiva, eu provavelmente recomendaria o uso da NULLdefinição de macro em C.

M2tM
fonte
tl; dr e embora você está certo sobre 0em C ++, que tem significado em C: (void *)0. 0é preferível NULLem C ++, porque erros de tipo podem ser uma PITA.
precisa saber é o seguinte
@ Matt: Mas NULLé zero de qualquer maneira.
GManNickG 30/09/10
@GMan: Não está no meu sistema: #define NULL ((void *)0)delinux/stddef.h
Matt Joiner
@ Mat: Em C ++. Você disse que 0 é preferível, mas NULLdeve ser zero.
GManNickG
Este é um bom ponto, esqueci de mencioná-lo e estou melhorando minha resposta sugerindo. O ANSI C pode ter NULL definido como ((void *) 0), o C ++ define NULL como 0. Não segui o padrão diretamente para isso, mas meu entendimento é que no C ++ NULL pode ser 0 ou 0L.
M2tM 30/09/10
19

Eu uso if (ptr), mas não vale a pena discutir isso completamente.

Eu gosto do meu jeito porque é conciso, embora outros digam == NULLfacilite a leitura e seja mais explícito. Vejo de onde eles vêm, apenas discordo das coisas extras que facilitam ainda mais. (Eu odeio a macro, então sou tendenciosa.) Depende de você.

Eu discordo do seu argumento. Se você não receber avisos de atribuições de forma condicional, precisará aumentar seus níveis de aviso. Simples assim. (E pelo amor de tudo o que é bom, não os troque.)

Observe que em C ++ 0x, podemos fazer o if (ptr == nullptr)que, para mim, é melhor. (Mais uma vez, eu odeio a macro. Mas nullptré legal.) Mas ainda o faço if (ptr), apenas porque é o que estou acostumado.

GManNickG
fonte
espero que mais 5 anos de experiência mudem de idéia :) se o C ++ / C tivesse um operador como if_exist (), faria sentido.
magulla
10

Francamente, não vejo por que isso importa. Qualquer um deles é bastante claro e qualquer pessoa com experiência moderada em C ou C ++ deve entender os dois. Um comentário, no entanto:

Se você planeja reconhecer o erro e não continuar executando a função (ou seja, você lançará uma exceção ou retornará um código de erro imediatamente), faça disso uma cláusula de guarda:

int f(void* p)
{
    if (!p) { return -1; }

    // p is not null
    return 0;
}

Dessa forma, você evita "código de seta".

James McNellis
fonte
alguém apontou aqui kukuruku.co/hub/programming/i-do-not-know-c que if(!p)é um comportamento indefinido. Poderia funcionar ou não.
David天宇Wong
@ David id Wong, se você está falando sobre o exemplo # 2 nesse link, o if(!p)comportamento não é indefinido, é a linha anterior a ele. E if(p==NULL)teria exatamente o mesmo problema.
Mark Ransom
8

Pessoalmente, sempre usei if (ptr == NULL)porque explicita minha intenção, mas neste momento é apenas um hábito.

O uso =no lugar de ==será capturado por qualquer compilador competente com as configurações de aviso corretas.

O ponto importante é escolher um estilo consistente para o seu grupo e cumpri-lo. Não importa para onde você vá, você acabará se acostumando, e a perda de atrito ao trabalhar no código de outras pessoas será bem-vinda.

Mark Ransom
fonte
7

Apenas mais um ponto a favor da foo == NULLprática: se foofor, digamos, um int *ou a bool *, o if (foo)cheque pode ser acidentalmente interpretado por um leitor como testando o valor do apontador, ou seja, como if (*foo). A NULLcomparação aqui é um lembrete de que estamos falando de um ponteiro.

Mas suponho que uma boa convenção de nomenclatura torne esse argumento discutível.

Daniel Hershcovich
fonte
4

Na verdade, eu uso as duas variantes.

Existem situações em que você primeiro verifica a validade de um ponteiro e, se for NULL, basta retornar / sair de uma função. (Eu sei que isso pode levar à discussão "uma função deve ter apenas um ponto de saída")

Na maioria das vezes, você verifica o ponteiro, faz o que deseja e resolve o caso de erro. O resultado pode ser o código recuado x-vezes feio com vários if's.

dwo
fonte
1
Eu pessoalmente odeio o código de seta que resultaria usando um ponto de saída. Por que não sair se eu já souber a resposta?
precisa saber é o seguinte
1
@ruslik: em C (ou C-com estilo de classe C ++), ter vários retornos dificulta a limpeza. No C ++ "real", obviamente isso não é um problema, porque sua limpeza é tratada pelo RAII, mas alguns programadores (por razões mais ou menos válidas) ficam presos no passado e recusam ou não têm permissão para confiar no RAII .
jalf
O @jalf nesses casos gotopode ser muito útil. Também em muitos casos, um malloc()que precisaria de limpeza poderia ser substituído por um mais rápido alloca(). Eu sei que eles não são recomendados, mas existem por uma razão (por mim, uma etiqueta bem nomeada gotoé muito mais limpa do que uma if/elseárvore ofuscada ).
ruslik
1
allocaé fora do padrão e completamente não seguro. Se falhar, seu programa simplesmente explode. Não há chance de recuperação e, como a pilha é relativamente pequena, é provável que haja falha. Em caso de falha, é possível que você esclareça a pilha e introduza vulnerabilidades comprometedoras de privilégios. Nunca use allocaou vlas, a menos que você tenha um pequeno limite no tamanho que será alocado (e, em seguida, é melhor usar apenas uma matriz normal).
R .. GitHub Pare de ajudar o gelo
4

A linguagem de programação C (K&R) solicita que você verifique nulo == ptr para evitar uma atribuição acidental.

Derek
fonte
23
K&R também perguntaria "o que é um ponteiro inteligente?" As coisas estão mudando no mundo C ++.
Alex Emelianov 29/09/10
2
ou melhor, as coisas já mudaram no mundo C ++ ";)
jalf
3
Onde a K&R declara isso (ref)? Eles nunca usam esse estilo eles mesmos. No capítulo 2 do K & R2, eles até recomendam if (!valid)acima if (valid == 0).
schot
6
Acalme-se, pessoal. Ele disse k & r, não K&R. Eles são obviamente menos famosos, pois suas iniciais nem são capitalizadas.
Jmucchiello
1
capitalizar apenas atrasa você
Derek
3

Se o estilo e o formato fizerem parte de suas revisões, deve haver um guia de estilo acordado para avaliar. Se houver, faça o que o guia de estilo diz. Se não houver um, detalhes como este devem ser deixados à medida que são escritos. É um desperdício de tempo e energia, e distrai o que as revisões de código realmente deveriam estar descobrindo. Sério, sem um guia de estilo, eu pressionaria para NÃO alterar um código como esse por uma questão de princípio, mesmo quando ele não usar a convenção de minha preferência.

E não que isso importe, mas minha preferência pessoal é if (ptr). O significado é mais imediatamente óbvio para mim do queif (ptr == NULL) .

Talvez ele esteja tentando dizer que é melhor lidar com condições de erro antes do caminho feliz? Nesse caso, ainda não concordo com o revisor. Não sei se existe uma convenção aceita para isso, mas, na minha opinião, a condição mais "normal" deve vir em primeiro lugar em qualquer declaração if. Dessa forma, tenho menos trabalho para descobrir o que é a função e como ela funciona.

A exceção a isso é se o erro fizer com que eu saia da função ou eu possa me recuperar dela antes de prosseguir. Nesses casos, eu lido com o erro primeiro:

if (error_condition)
  bail_or_fix();
  return if not fixed;

// If I'm still here, I'm on the happy path

Ao lidar com a condição incomum na frente, posso cuidar dela e depois esquecê-la. Mas se eu não conseguir voltar ao caminho feliz lidando com isso de frente, ele deve ser tratado depois o caso principal, pois torna o código mais compreensível. Na minha opinião.

Mas se não estiver em um guia de estilo, é apenas a minha opinião, e sua opinião é igualmente válida. Padronize ou não. Não deixe um revisor pseudo-padronizar apenas porque ele tem uma opinião.

Darryl
fonte
1

Este é um dos fundamentos das duas linguagens que os ponteiros avaliam para um tipo e valor que pode ser usado como expressão de controle, boolem C ++ e intem C. Basta usá-lo.

Jens Gustedt
fonte
1
  • Ponteiros não são booleanos
  • Os compiladores modernos de C / C ++ emitem um aviso quando você escreve if (foo = bar)acidentalmente.

Por isso prefiro

if (foo == NULL)
{
    // null case
}
else
{
    // non null case
}

ou

if (foo != NULL)
{
    // non null case
}
else
{
    // null case
}

No entanto, se eu estivesse escrevendo um conjunto de diretrizes de estilo, não colocaria coisas assim, colocaria coisas como:

Certifique-se de fazer uma verificação nula no ponteiro.

JeremyP
fonte
2
É verdade que os ponteiros não são booleanos, mas em C, as instruções if não aceitam booleanos: eles usam expressões inteiras.
Ken
@ Ken: é porque C está quebrado a esse respeito. Conceitualmente, é uma expressão booleana e (na minha opinião) deve ser tratada como tal.
JeremyP
1
Alguns idiomas possuem instruções if que testam apenas nulo / não nulo. Alguns idiomas têm instruções if que apenas testam um número inteiro para sinal (um teste de três vias). Não vejo razão para considerar C "quebrado" porque eles escolheram um conceito diferente que você gosta. Há muitas coisas que eu odeio no C, mas isso apenas significa que o modelo do programa C não é o mesmo que o meu modelo mental, não que nenhum de nós (eu ou C) esteja quebrado.
Ken
@ Ken: Um booleano não é um número ou um ponteiro conceitualmente , não importa qual idioma.
JeremyP
1
Eu não disse que um booleano era um número ou ponteiro. Eu disse que não há razão para insistir que uma declaração if deva ou possa assumir apenas uma expressão booleana e ofereci contra-exemplos, além do que está em questão. Muitos idiomas usam algo diferente de um booleano, e não apenas da maneira "zero / diferente de zero" de C. Na computação, ter uma instrução if que aceite (apenas) um booleano é um desenvolvimento relativamente recente.
Ken
1

Sou um grande fã do fato de que o C / C ++ não verifica tipos nas condições booleanas em if , fore whiledeclarações. Eu sempre uso o seguinte:

if (ptr)

if (!ptr)

mesmo em números inteiros ou outro tipo que converte em bool:

while(i--)
{
    // Something to do i times
}

while(cin >> a >> b)
{
    // Do something while you've input
}

Codificar neste estilo é mais legível e mais claro para mim. Apenas minha opinião pessoal.

Recentemente, enquanto trabalhava no microcontrolador OKI 431, observei o seguinte:

unsigned char chx;

if (chx) // ...

é mais eficiente que

if (chx == 1) // ...

porque, posteriormente, o compilador precisará comparar o valor de chx a 1. Onde chx é apenas um sinalizador verdadeiro / falso.

Donotalo
fonte
1

A maioria dos compiladores que usei avisará pelo menos a ifatribuição sem mais açúcar de sintaxe, portanto, não compro esse argumento. Dito isto, usei tanto profissionalmente como também não tenho preferência. A == NULLé definitivamente mais claro que na minha opinião.

Michael Dorgan
fonte