Comprobación de puntero NULL en C / C ++ [cerrado]

153

En una revisión de código reciente, un contribuyente está tratando de exigir que todas las NULLverificaciones en los punteros se realicen de la siguiente manera:

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

en vez de

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

Estoy de acuerdo en que su manera es un poco más clara en el sentido de que está diciendo explícitamente "Asegúrese de que este puntero no sea NULL", pero lo contrarrestaría diciendo que cualquiera que esté trabajando en este código entendería que usar una variable de puntero en un ifafirmación está implícita comprobando NULL. También siento que el segundo método tiene una menor posibilidad de introducir un error del tipo:

if (some_ptr = NULL)

lo cual es un dolor absoluto para encontrar y depurar.

¿Qué camino prefieres y por qué?

Bryan Marble
fuente
32
Tener un estilo consistente a veces es más importante que el estilo que elijas.
Mark Ransom
15
@Mark: La consistencia es siempre el factor más importante de tu estilo.
sbi
13
"También creo que el segundo método tiene una menor posibilidad de introducir un error del tipo: if (some_ptr = NULL)" Es por eso que algunas personas usan if (NULL == some_ptr), no se puede asignar a NULL para que pueda obtener Un error de compilación.
user122302
14
La mayoría de los compiladores en estos días advierten sobre la asignación de condicionales; desafortunadamente, muchos desarrolladores ignoran las advertencias del compilador.
Mike Ellery
10
No estoy de acuerdo con la afirmación anterior de que la coherencia es lo que importa. Esto no tiene nada que ver con la consistencia; No es el buen tipo de todos modos. Esta es el área donde debemos dejar que los programadores expresen su propio estilo. Si hay una persona que no comprende inmediatamente ninguno de los dos formularios, entonces debe dejar de leer el código de inmediato y considerar un cambio de carrera. Diré más que eso: si hay una consistencia cosmética que no puedes aplicar automáticamente con un script, entonces quítala. El costo de drenar la moral humana es MUCHO más importante que esas estúpidas decisiones que las personas toman en una reunión.
wilhelmtell

Respuestas:

203

En mi experiencia, se prefieren las pruebas de la forma if (ptr)o if (!ptr). No dependen de la definición del símbolo NULL. No exponen la oportunidad de la asignación accidental. Y son claros y concisos.

Editar: como SoapBox señala en un comentario, son compatibles con las clases de C ++, como los auto_ptrobjetos que actúan como punteros y que proporcionan una conversión boolpara habilitar exactamente este idioma. Para estos objetos, una comparación explícita NULLtendría que invocar una conversión a puntero que puede tener otros efectos secundarios semánticos o ser más costoso que la simple verificación de existencia que boolimplica la conversión.

Prefiero el código que dice lo que significa sin texto innecesario. if (ptr != NULL)tiene el mismo significado if (ptr)pero a costa de una especificidad redundante. Lo siguiente lógico es escribir if ((ptr != NULL) == TRUE)y de esa manera se encuentra la locura. El lenguaje C es claro que un booleano probado por if, whileo similar tiene un significado específico de un valor distinto de cero es verdadero y cero es falso. La redundancia no lo hace más claro.

RBerteig
fuente
23
Además, son compatibles con las clases de envoltura de puntero (shared_ptr, auto_ptr, scoped_tr, etc.) que generalmente anulan el operador bool (o safe_bool).
SoapBox
2
Estoy votando esto como la "respuesta" simplemente debido a la referencia a auto_ptr que se agrega a la discusión. Después de escribir la pregunta, está claro que esto es realmente más subjetivo que cualquier otra cosa y probablemente no sea adecuado para una "respuesta" de stackoverflow ya que cualquiera de estos podría ser "correcto" dependiendo de las circunstancias.
Bryan Marble
2
@Bryan, lo respeto, y si aparece una respuesta "mejor", no dude en mover la marca de verificación según sea necesario. En cuanto a la subjetividad, sí, es subjetiva. Pero es al menos una discusión subjetiva donde hay cuestiones objetivas que no siempre son obvias cuando se plantea la pregunta. La línea está borrosa. Y subjetivo ... ;-)
RBerteig
1
Una cosa importante a tener en cuenta: hasta hace poco, estaba a favor de escribir "if (ptr)" en lugar de "if (ptr! = NULL)" o "if (ptr! = Nullptr)", ya que es más conciso, por lo que hace El código más legible. Pero acabo de descubrir que la comparación genera (respectivamente) advertencia / error para la comparación con NULL / nullptr en compiladores recientes. Entonces, si desea verificar un puntero, es mejor hacer "if (ptr! = NULL)" en lugar de "if (ptr)". Si declara ptr por error como int, la primera comprobación generará una advertencia / error, mientras que la segunda se compilará sin ninguna advertencia.
Arnaud
1
@David 天宇 Wong No, eso no es lo que se quiso decir. El ejemplo en esa página es la secuencia de dos líneas int y = *x; if (!x) {...}donde el optimizador puede razonar que, dado *xque no estaría definido si xes NULL, no debe ser NULL y, por !xlo tanto, debe ser FALSE. La expresión no!ptr es comportamiento indefinido. En el ejemplo, si la prueba se realizó antes de usarla , entonces el optimizador estaría equivocado al eliminar la prueba. *x
RBerteig
52

if (foo)Es lo suficientemente claro. Úsalo.

wilx
fuente
25

Comenzaré con esto: la consistencia es el rey, la decisión es menos importante que la consistencia en su base de código.

En C ++

NULL se define como 0 o 0L en C ++.

Si ha leído El lenguaje de programación C ++, Bjarne Stroustrup sugiere usar 0explícitamente para evitar la NULLmacro al hacer la asignación , no estoy seguro de si hizo lo mismo con las comparaciones, ha pasado un tiempo desde que leí el libro, creo que simplemente lo hizoif(some_ptr) sin una comparación explícita pero estoy confuso en eso.

La razón de esto es que la NULLmacro es engañosa (como lo son casi todas las macros) en realidad es 0literal, no un tipo único como su nombre lo sugiere. Evitar macros es una de las pautas generales en C ++. Por otra parte,0 parece un número entero y no lo es cuando se compara o se asigna a punteros. Personalmente, podría ir en cualquier dirección, pero por lo general omito la comparación explícita (aunque a algunas personas no les gusta esto, probablemente por eso hay un colaborador que sugiere un cambio de todos modos).

Independientemente de los sentimientos personales, esta es en gran medida una elección del menor mal, ya que no hay un método correcto.

Esto es claro y un idioma común y lo prefiero, no hay posibilidad de asignar accidentalmente un valor durante la comparación y se lee claramente:

if(some_ptr){;}

Esto está claro si sabe que some_ptres un tipo de puntero, pero también puede parecer una comparación de enteros:

if(some_ptr != 0){;}

Esto es claro, en casos comunes tiene sentido ... Pero es una abstracción permeable, en NULLrealidad es 0literal y podría terminar siendo mal utilizado fácilmente:

if(some_ptr != NULL){;}

C ++ 0x tiene nullptr, que ahora es el método preferido ya que es explícito y preciso, solo tenga cuidado con la asignación accidental:

if(some_ptr != nullptr){;}

Hasta que pueda migrar a C ++ 0x, diría que es una pérdida de tiempo preocuparse por cuál de estos métodos utiliza, todos son insuficientes, razón por la cual se inventó nullptr (junto con problemas de programación genéricos que surgieron con el reenvío perfecto .) Lo más importante es mantener la consistencia.

C ª

C es una bestia diferente.

En C NULL se puede definir como 0 o como ((void *) 0), C99 permite la implementación de constantes de puntero nulo definidas. Por lo tanto, en realidad se reduce a la definición de implementación de NULL y tendrá que inspeccionarlo en su biblioteca estándar.

Las macros son muy comunes y, en general, se usan mucho para compensar las deficiencias en el soporte de programación genérica en el lenguaje y otras cosas también. El lenguaje es mucho más simple y la dependencia del preprocesador es más común.

Desde esta perspectiva, probablemente recomendaría usar la NULLdefinición de macro en C.

M2tM
fuente
tl; dr y aunque tienes razón en 0en C ++, tiene un significado en C: (void *)0. 0es preferible a NULLen C ++, porque los errores de tipo pueden ser un PITA.
Matt Joiner
@ Matt: Pero NULLes cero de todos modos.
GManNickG
@GMan: No en mi sistema: #define NULL ((void *)0)delinux/stddef.h
Matt Joiner
@ Matt: en C ++. Dijiste que 0 es preferible, pero NULLdebe ser cero.
GManNickG
Este es un buen punto, olvidé mencionarlo y estoy mejorando mi respuesta al sugerirlo. ANSI C puede tener NULL definido como ((void *) 0), C ++ define NULL como 0. No he rastreado el estándar directamente, pero entiendo que en C ++ NULL puede ser 0 o 0L.
M2tM
19

Yo uso if (ptr), pero esto no vale la pena discutir por completo.

Me gusta mi camino porque es conciso, aunque otros dicen == NULLque es más fácil de leer y más explícito. Veo de dónde vienen, solo estoy en desacuerdo, las cosas adicionales lo hacen más fácil. (Odio la macro, así que soy parcial). Depende de usted.

No estoy de acuerdo con tu argumento. Si no recibe advertencias para las asignaciones de manera condicional, debe aumentar sus niveles de advertencia. Simple como eso. (Y por amor a todo lo que es bueno, no los cambie).

Tenga en cuenta que en C ++ 0x, podemos hacer if (ptr == nullptr), lo que para mí se lee mejor. (Nuevamente, odio la macro. Pero nullptres agradable.) Sin if (ptr)embargo, todavía lo hago , solo porque es a lo que estoy acostumbrado.

GManNickG
fuente
espero que 5 años extra de experiencia cambien de opinión :) si C ++ / C tuviera un operador como if_exist (), entonces tendría sentido.
magulla
10

Francamente, no veo por qué importa. Cualquiera de los dos es bastante claro y cualquier persona con experiencia moderada en C o C ++ debería entender ambos. Un comentario, sin embargo:

Si planea reconocer el error y no continuar ejecutando la función (es decir, va a lanzar una excepción o devolver un código de error inmediatamente), debe convertirla en una cláusula de protección:

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

    // p is not null
    return 0;
}

De esta manera, evita el "código de flecha".

James McNellis
fuente
alguien señaló aquí kukuruku.co/hub/programming/i-do-not-know-c que if(!p)es un comportamiento indefinido. Podría funcionar o no.
David 天宇 Wong
@David 天宇 Wong si estás hablando del ejemplo # 2 en ese enlace, if(!p)no es el comportamiento indefinido, es la línea anterior. Y if(p==NULL)tendría exactamente el mismo problema.
Mark Ransom
8

Personalmente, siempre lo he usado if (ptr == NULL)porque hace que mi intención sea explícita, pero en este punto es solo un hábito.

El uso =en lugar de ==será capturado por cualquier compilador competente con la configuración de advertencia correcta.

El punto importante es elegir un estilo consistente para su grupo y atenerse a él. No importa en qué dirección vayas, eventualmente te acostumbrarás, y la pérdida de fricción cuando trabajes en el código de otras personas será bienvenida.

Mark Ransom
fuente
7

Solo un punto más a favor de la foo == NULLpráctica: si fooes, digamos, una int *o una bool *, entonces el if (foo)cheque puede ser interpretado accidentalmente por un lector como si probara el valor del pointee, es decir, como if (*foo). La NULLcomparación aquí es un recordatorio de que estamos hablando de un puntero.

Pero supongo que una buena convención de nombres hace que este argumento sea discutible.

Daniel Hershcovich
fuente
4

En realidad, uso ambas variantes.

Hay situaciones en las que primero verifica la validez de un puntero, y si es NULL, simplemente regresa / sale de una función. (Sé que esto puede llevar a la discusión "si una función tiene solo un punto de salida")

La mayoría de las veces, verifica el puntero, luego hace lo que desea y luego resuelve el caso de error. El resultado puede ser el feo código x-times sangrado con múltiples if's.

dwo
fuente
1
Personalmente odio el código de flecha que resultaría usando un punto de salida. ¿Por qué no salir si ya sé la respuesta?
ruslik
1
@ruslik: en C (o estilo C-with-classes C ++), tener múltiples retornos dificulta la limpieza. En C ++ "real" eso obviamente no es un problema porque RAII maneja su limpieza, pero algunos programadores están (por razones más o menos válidas) atascados en el pasado y rechazan o no pueden confiar en RAII .
jalf
@jalf en tales casos a gotopuede ser muy útil. Además, en muchos casos, una malloc()que necesitaría limpieza podría ser reemplazada por una más rápida alloca(). Sé que no se recomiendan, pero existen por una razón (para mí, una etiqueta bien nombrada gotoes mucho más limpia que un if/elseárbol ofuscado ).
ruslik
1
allocano es estándar y no es completamente seguro. Si falla, su programa simplemente explota. No hay posibilidad de recuperación, y dado que la pila es relativamente pequeña, es probable que falle. En caso de falla, es posible que golpee el montón e introduzca vulnerabilidades que comprometan los privilegios. Nunca use allocao vlas a menos que tenga un pequeño límite en el tamaño que se asignará (y entonces también podría usar una matriz normal).
R .. GitHub DEJA DE AYUDAR AL HIELO
4

El lenguaje de programación C (K&R) le haría verificar null == ptr para evitar una asignación accidental.

Derek
fuente
23
K&R también preguntaría "¿qué es un puntero inteligente?" Las cosas están cambiando en el mundo de C ++.
Alex Emelianov
2
o más bien, las cosas ya han cambiado en el mundo C ++ ";)
jalf
3
¿Dónde dicen K&R que (ref)? Nunca usan este estilo ellos mismos. En K & R2, capítulo 2, incluso recomiendan lo if (!valid)anterior if (valid == 0).
schot
66
Cálmate, amigos. Dijo k & r, no K&R. Obviamente son menos famosos ya que sus iniciales ni siquiera están en mayúscula.
jmucchiello
1
capitalizar solo te ralentiza
Derek
3

Si el estilo y el formato van a ser parte de sus revisiones, debe haber una guía de estilo acordada para comparar. Si hay uno, haga lo que dice la guía de estilo. Si no hay uno, los detalles como este deben dejarse tal como están escritos. Es una pérdida de tiempo y energía, y distrae de lo que las revisiones de código realmente deberían descubrir. En serio, sin una guía de estilo, presionaría para NO cambiar un código como este por principio, incluso si no usa la convención que prefiero.

Y no es que importe, pero mi preferencia personal es if (ptr). El significado es más obvio para mí que incluso if (ptr == NULL).

¿Quizás está tratando de decir que es mejor manejar las condiciones de error antes del camino feliz? En ese caso, todavía no estoy de acuerdo con el revisor. No sé si hay una convención aceptada para esto, pero en mi opinión, la condición más "normal" debería ser lo primero en cualquier declaración if. De esa manera, tengo que investigar menos para averiguar de qué se trata la función y cómo funciona.

La excepción a esto es si el error hace que me retire de la función, o puedo recuperarme de ella antes de continuar. En esos casos, primero manejo el error:

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

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

Al tratar la condición inusual por adelantado, puedo ocuparme de ella y luego olvidarla. Pero si no puedo volver al camino feliz manejándolo por adelantado, entonces debería ser manejado después del caso principal porque hace que el código sea más comprensible. En mi opinión.

Pero si no está en una guía de estilo, entonces es solo mi opinión, y su opinión es igual de válida. O estandarizar o no. No permita que un crítico pseudo-estandarice solo porque tiene una opinión.

Darryl
fuente
1

Este es uno de los fundamentos de ambos lenguajes que los punteros evalúan para un tipo y valor que puede usarse como una expresión de control, boolen C ++ y inten C. Simplemente utilícelo.

Jens Gustedt
fuente
1
  • Los punteros no son booleanos
  • Los compiladores modernos de C / C ++ emiten una advertencia cuando escribes if (foo = bar)por accidente.

Por eso prefiero

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

o

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

Sin embargo, si estuviera escribiendo un conjunto de pautas de estilo, no estaría poniendo cosas como esta, estaría poniendo cosas como:

Asegúrese de hacer una comprobación nula en el puntero.

JeremyP
fuente
2
Es cierto que los punteros no son booleanos, pero en C, las sentencias if no toman booleanos: toman expresiones enteras.
Ken
@ Ken: eso se debe a que C está roto en ese sentido. Conceptualmente, es una expresión booleana y (en mi opinión) debería tratarse como tal.
JeremyP
1
Algunos lenguajes tienen sentencias if que solo prueban nulo / no nulo. Algunos idiomas tienen sentencias if que solo prueban un entero para firmar (una prueba de 3 vías). No veo ninguna razón para considerar a C "roto" porque eligieron un concepto diferente que te gusta. Hay muchas cosas que odio de C, pero eso solo significa que el modelo del programa C no es el mismo que mi modelo mental, no que ninguno de nosotros (yo o C) esté roto.
Ken
@Ken: un booleano no es un número o un puntero conceptual , no importa qué idioma.
JeremyP
1
No dije que un booleano era un número o un puntero. Dije que no hay razón para insistir en que una declaración if debería o solo podría tomar una expresión booleana, y ofreció contraejemplos, además de la que está a la mano. Muchos lenguajes toman algo más que un booleano, y no solo en la forma "cero / no cero" de C. En informática, tener un enunciado if que acepte (solo) un booleano es un desarrollo relativamente reciente.
Ken
1

Soy un gran fan del hecho de que C / C ++ no comprueba los tipos en las condiciones booleanas en if, fory whiledeclaraciones. Siempre uso lo siguiente:

if (ptr)

if (!ptr)

incluso en enteros u otro tipo que se convierte en bool:

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

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

La codificación en este estilo es más legible y más clara para mí. Solo mi opinión personal.

Recientemente, mientras trabajaba en el microcontrolador OKI 431, noté que lo siguiente:

unsigned char chx;

if (chx) // ...

es más eficiente que

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

porque en un caso posterior el compilador tiene que comparar el valor de chx a 1. Donde chx es solo un indicador verdadero / falso.

Donotalo
fuente
1

La mayoría de los compiladores que he usado al menos advertirán sobre la iftarea sin más azúcar de sintaxis, por lo que no compro ese argumento. Dicho esto, he usado ambos profesionalmente y no tengo preferencia por ninguno. El == NULLes definitivamente más claro, aunque en mi opinión.

Michael Dorgan
fuente