Comprobando el puntero NULL en C / C ++

En una revisión de código reciente, un colaborador está tratando de hacer cumplir que todas las comprobaciones NULL en los punteros se lleven a cabo de la siguiente manera:

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

en lugar de

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

Estoy de acuerdo en que su camino es un poco más claro en el sentido de que está diciendo explícitamente “Asegúrate de que este puntero no sea NULO”, pero lo contrarrestaría diciendo que cualquiera que esté trabajando en este código entendería que usar una variable de puntero en un if statement está comprobando implícitamente NULL . También creo que el segundo método tiene una menor posibilidad de introducir un error de la clase:

 if (some_ptr = NULL) 

que es solo un dolor absoluto de encontrar y depurar.

¿De qué manera prefieres y por qué?

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 para la asignación accidental. Y son claros y concisos.

Editar: como señala SoapBox en un comentario, son compatibles con clases de C ++ como auto_ptr que son objetos que actúan como punteros y que proporcionan una conversión a bool para habilitar exactamente este modismo. Para estos objetos, una comparación explícita a NULL tendría que invocar una conversión a puntero que puede tener otros efectos secundarios semánticos o ser más costosa que la comprobación de existencia simple que implica la conversión de bool .

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

if (foo) es lo suficientemente claro. Úselo.

Comenzaré con esto: la coherencia 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 has leído el lenguaje de progtwigción C ++ Bjarne Stroustrup sugiere usar 0 explícitamente para evitar la macro NULL al hacer la asignación , no estoy seguro de si hizo lo mismo con las comparaciones, hace mucho tiempo que no leo el libro, creo que él acabo de hacer if(some_ptr) sin una comparación explícita, pero estoy confundido con eso.

La razón de esto es que la macro NULL es engañosa (como casi todas las macros), en realidad es 0 literal, no un tipo único como su nombre lo sugiere. Evitar macros es una de las pautas generales en C ++. Por otro lado, 0 parece un número entero y no se compara con los punteros ni se los asigna a ellos. Personalmente, podría ir de cualquier manera, pero normalmente omito la comparación explícita (aunque a algunas personas no les gusta esto, por lo que es probable que un colaborador sugiera un cambio de todos modos).

Independientemente de los sentimientos personales, esto es en gran medida una elección de la menor maldad, ya que no hay un solo 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 es claro si usted sabe que some_ptr es un tipo de puntero, pero también puede parecer una comparación entera:

 if(some_ptr != 0){;} 

Esto es claro, en casos comunes tiene sentido … Pero es una abstracción con filtraciones, NULL es en realidad 0 literal 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, argumentaría que es una pérdida de tiempo preocuparse por cuáles de estos métodos utiliza, son insuficientes, por lo que nullptr fue inventado (junto con problemas generics de progtwigción que surgieron con un reenvío perfecto). .) Lo más importante es mantener la coherencia.

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 trata de la definición de implementación de NULL y tendrá que inspeccionarla en su biblioteca estándar.

Las macros son muy comunes y en general se usan mucho para compensar las deficiencias en el soporte de progtwigción genérica en el lenguaje y otras cosas también. El lenguaje es mucho más simple y la confianza en el pre-procesador es más común.

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

Uso if (ptr) , pero esto no vale la pena discutir sobre esto.

Me gusta a mi manera porque es conciso, aunque otros dicen que == NULL hace más fácil de leer y más explícito. Veo de dónde vienen, simplemente no estoy de acuerdo en que las cosas adicionales lo hagan más fácil. (Odio el macro, así que estoy predispuesto.) Depende de ti.

No estoy de acuerdo con tu argumento. Si no recibe advertencias para asignaciones en un condicionamiento, debe activar sus niveles de advertencia. Simple como eso. (Y por amor a todo lo que es bueno, no los cambies).

Tenga en cuenta que en C ++ 0x, podemos hacer if (ptr == nullptr) , que para mí es más agradable de leer. (Una vez más, odio el macro. Pero nullptr es agradable.) Aún así lo hago if (ptr) , solo porque es a lo que estoy acostumbrado.

Francamente, no veo por qué es importante. Cualquiera de los dos es bastante claro y cualquier persona medianamente experimentada con 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 guardia:

 int f(void* p) { if (!p) { return -1; } // p is not null return 0; } 

De esta forma, evitas el “código de flecha”.

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 sangrado x veces con múltiples if’s.

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

Usar = en lugar de == será capturado por cualquier comstackdor competente con la configuración de advertencia correcta.

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

Solo un punto más a favor de la práctica foo == NULL : si foo es, digamos, un int * o un bool * , entonces el control if (foo) puede ser interpretado accidentalmente por un lector como prueba del valor del pointee, es decir, como if (*foo) . La comparación NULL 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.

El Lenguaje de progtwigción C (K & R) haría que compruebe si null == ptr para evitar una asignación accidental.

Si el estilo y el formato van a formar parte de sus revisiones, debe haber una guía de estilo acordada para medir. Si hay uno, haz lo que dice la guía de estilo. Si no hay uno, los detalles como este se deben dejar tal como están escritos. Es una pérdida de tiempo y energía, y distrae de lo que las revisiones de códigos realmente deberían descubrir. En serio, sin una guía de estilo, presionaría para NO cambiar código como este por cuestión de principios, incluso cuando no use 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) .

¿Tal vez 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 statement. De esa manera, tengo que profundizar menos para descubrir de qué se trata la función y cómo funciona.

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

 if (error_condition) bail_or_fix(); return if not fixed; // If I'm still here, I'm on the happy path 

Al tratar con la condición inusual desde el principio, puedo encargarme de eso y luego olvidarme de ello. Pero si no puedo volver al camino feliz manejándolo desde el principio, entonces debe manejarse 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 tu opinión es igual de válida. Ya sea estandarizar o no. No dejes que un crítico pseudoestandarice solo porque tiene una opinión.

Creo que esto es bastante claro:

 if( !some_ptr ) { // handle null-pointer error } else { // proceed } 

Léelo como “Si no hay un puntero …”

Además, es conciso y no tiene peligro de asignaciones accidentales.

Este es uno de los fundamentos de ambos lenguajes que los punteros evalúan a un tipo y valor que se puede usar como una expresión de control, bool en C ++ e int en C. Simplemente utilícelo.

Soy un gran admirador del hecho de que C / C ++ no comprueba los tipos en las condiciones booleanas en declaraciones if , for y while . Yo siempre uso lo siguiente:

 if (ptr) if (!ptr) 

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

 while(i--) { // Something to do i times } while(cin >> a >> b) { // Do something while you've input } 

La encoding 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, me di cuenta de lo siguiente:

 unsigned char chx; if (chx) // ... 

es más eficiente que

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

porque en el caso posterior el comstackdor tiene que comparar el valor de chx a 1. Donde chx es solo una bandera de verdadero / falso.

La mayoría de los comstackdores que he usado al menos advertirán sobre la asignación if sin más syntax de azúcar, por lo que no creo ese argumento. Dicho esto, he usado ambos profesionalmente y no tengo preferencia por ninguno de los dos. Sin embargo, el == NULL es definitivamente más claro en mi opinión.

  • Los punteros no son booleanos
  • Los comstackdores C / C ++ modernos emiten una advertencia cuando se escribe if (foo = bar) por accidente.

Por lo tanto 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 en él, estaría poniendo cosas como:

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