En C o C ++, ¿debo verificar los parámetros del puntero contra NULL / nullptr?

Esta pregunta fue inspirada por esta respuesta .

Siempre he tenido la filosofía de que el destinatario nunca es responsable cuando el que llama hace algo estúpido, como pasar parámetros no válidos. He llegado a esta conclusión por varias razones, pero quizás la más importante proviene de este artículo :

Todo lo no definido no está definido.

Si una función no dice en sus documentos que es válida pasar nullptr , entonces será mejor que no pases nullptr a esa función. No creo que sea responsabilidad del destinatario tratar con tales cosas.

Sin embargo, sé que van a haber algunos que no están de acuerdo conmigo. Tengo curiosidad de saber si debo verificar estas cosas y por qué.

Si bien, en general, no veo el valor de detectar NULL (¿por qué NULL y no otra dirección no válida?) Para una API pública, probablemente todavía lo haga simplemente porque muchos progtwigdores C y C ++ esperan ese comportamiento.

Si va a verificar los argumentos del puntero NULL donde no ha firmado un contrato para aceptarlos e interpretarlos, hágalo con una assert , no con un retorno de error condicional. De esta forma, los errores en la persona que llama serán detectados de inmediato y pueden corregirse, y facilita la desactivación de la sobrecarga en las comstackciones de producción. Sin embargo, cuestiono el valor de la assert excepto como documentación; una segfault de desreferenciar el puntero NULL es igual de eficaz para la depuración.

Si devuelve un código de error a un interlocutor que ya ha probado que tiene errores , lo más probable es que la persona que llama ignore el error, y las cosas malas ocurrirán mucho más adelante cuando la causa original del error se haya vuelto difícil o imposible rastrear. ¿Por qué es razonable suponer que la persona que llama ignorará el error que usted devuelve? Porque la persona que llama ya ignoró el retorno de error de malloc o fopen o alguna otra función de asignación específica de la biblioteca que devolvió NULL para indicar un error.

En C ++, si no quiere aceptar punteros NULL, entonces no se arriesgue : acepte una referencia en su lugar.

El principio de defensa en profundidad dice que sí. Si esto es una API externa, entonces es totalmente esencial. De lo contrario, al menos una afirmación para ayudar a la depuración del uso indebido de su API.

Puede documentar el contrato hasta que se ponga triste, pero no puede evitar el mal uso incorrecto o malicioso de su función en el código de llamada. La decisión que debe tomar es cuál es el costo probable del uso indebido.

En mi opinión, no es una cuestión de responsabilidad. Es una cuestión de robustez.

A menos que tenga control total sobre la persona que llama y debo optimizar incluso para la mejora de velocidad minuto, siempre compruebo NULL.

Mi filosofía es: tus usuarios deberían poder cometer errores, tu equipo de progtwigción no debería hacerlo.

Lo que esto significa es que el único lugar donde debe verificar los parámetros no válidos, incluido NULL, está en la interfaz de usuario de nivel superior. En cualquier lugar en el que el usuario pueda ingresar su código, debe verificar si hay errores y manejarlos de la forma más elegante posible.

En cualquier otro lugar, debe usar ASSERTS para asegurarse de que los progtwigdores estén usando las funciones correctamente.

Si está escribiendo una API, solo las funciones de nivel superior deben detectar y manejar las entradas incorrectas. No tiene sentido seguir buscando un puntero NULL de tres o cuatro niveles en la stack de llamadas.

Me inclino mucho por el lado de ‘no confíes en la entrada de tu usuario para no explotar tu sistema’ y en la progtwigción defensiva en general. Desde que hice las API en una vida pasada, he visto a los usuarios de las bibliotecas pasar punteros nulos y luego se producen lockings de aplicaciones.

Si se trata realmente de una biblioteca interna y soy la única persona (o solo unas pocas) que tengo la capacidad de usarla, entonces podría reducir las comprobaciones de punteros nulos siempre que todos acepten acatar los contratos generales. No puedo confiar en que la base de usuarios en general se adhiera a eso.

La respuesta será diferente para C y C ++.

C ++ tiene referencias. La única diferencia entre pasar un puntero y pasar una referencia es que el puntero puede ser nulo. Entonces, si el escritor de la función llamada espera un argumento de puntero y se olvida de hacer algo cuerdo cuando es nulo, es tonto, loco o escribe C-con-clases.

De cualquier manera, esto no es una cuestión de quién lleva la responsabilidad sombrero. Para escribir un buen software, los dos progtwigdores deben cooperar, y es responsabilidad de todos los progtwigdores 1 ° evitar casos especiales que requerirían este tipo de decisión y 2 ° cuando eso falla, escribir el código que explota en un forma no ambigua y documentada para ayudar con la depuración.

Así que, seguro, puedes señalar y reírte de la persona que llama porque cometió un error y “todo lo no definido no está definido” y tuvo que pasar una hora depurando un simple error de puntero nulo, pero tu equipo desperdició un tiempo precioso en eso.

Soy pro progtwigción defensiva.

A menos que pueda perfilar que estas comprobaciones nullptr suceden en un cuello de botella de su aplicación … (en tales casos es concebible que uno no debería hacer esas puntas de prueba de valor en esos puntos)

pero en general, comparar una int con 0 es una operación realmente barata . Creo que es una pena permitir potenciales errores de locking en lugar de consumir tan poca CPU.

así que: ¡prueba tus punteros contra NULL!

Creo que deberías esforzarte por escribir código que sea robusto para cada situación concebible. Pasar un puntero NULL a una función es muy común; por lo tanto, su código debe verificarlo y tratar con él, generalmente devolviendo un valor de error. Las funciones de la biblioteca NO deben bloquear una aplicación.

Para C ++, si su función no acepta nullpointer, entonces use un argumento de referencia. En general.

Hay algunas excepciones Por ejemplo, muchas personas, incluyéndome a mí, piensan que es mejor con el argumento del puntero cuando el argumento real será más naturalmente un puntero, especialmente cuando la función almacena una copia del puntero. Incluso cuando la función no es compatible con el argumento nullpointer.

Dependerá de cuánto defender contra el argumento inválido, incluido que depende de la opinión subjetiva y de la sensación visceral.

Saludos y hth.,

Una cosa que debes tener en cuenta es qué sucede si un llamador HACE mal uso de tu API. En el caso de pasar punteros NULL, el resultado es un locking obvio, por lo que está bien no verificarlo. Cualquier mal uso será evidente para el desarrollador del código de llamada.

La infame debacle glibc es otra cosa completamente distinta. El mal uso resultó en un comportamiento realmente útil para la persona que llama, y ​​la API se mantuvo así durante décadas. Luego lo cambiaron.

En este caso, los desarrolladores de API deberían haber comprobado los valores con un assert o algún mecanismo similar. Pero no puedes retroceder en el tiempo para corregir un error. El llanto y el crujir de dientes eran inevitables. Lea todo al respecto aquí .

Si no quiere un NULL, entonces no convierta el parámetro en un puntero.
Al usar una referencia, usted garantiza que el objeto no será NULL.

No creo que sea responsabilidad del destinatario tratar con tales cosas

Si no asume esta responsabilidad, podría generar malos resultados, como eliminar referencias de punteros NULL . El problema es que siempre asume implícitamente esta responsabilidad. Es por eso que prefiero el manejo elegante.

En mi opinión, es responsabilidad del destinatario hacer cumplir su contrato.

Si el destinatario no debería aceptar NULL , entonces debería assert eso.

De lo contrario, el destinatario debería comportarse bien cuando se le entrega un NULL . Es decir, o bien debería ser funcionalmente no operativa, devolver un código de error o asignar su propia memoria, según el contrato que haya especificado. Debería hacer lo que parezca más sensato desde la perspectiva de quien llama.

Como usuario de la API, quiero poder seguir usándolo sin que el progtwig se cuelgue; Quiero ser capaz de recuperar al menos o cerrar con gracia en el peor.

Sí, debe verificar si hay punteros nulos. No desea bloquear una aplicación porque el desarrollador arruinó algo.

El que realiza operaciones no válidas en datos no válidos o inexistentes, solo merece que su estado de sistema se vuelva inválido.

Considero una completa tontería que las funciones que esperan entrada deben verificar NULL. O cualquier otro valor para ese asunto. El único trabajo de una función es hacer una tarea en función de su entrada o scope-estado, nada más. Si no tiene una entrada válida, o no tiene ninguna entrada, no llame a la función. Además, una comprobación NULL no detecta los otros millones y millones de posibles valores no válidos. Usted sabe de antemano que pasará NULL, entonces ¿por qué todavía lo pasará, perderá valiosos ciclos en otra llamada a función con el paso de parámetros, una comparación en función de algún puntero, y luego comprobará la salida de la función nuevamente para asegurarse de que no es exitoso? . Claro, podría haberlo hecho cuando tenía 6 años en 1982, pero esos días hace tiempo que desaparecieron.

Por supuesto, hay un argumento para las API públicas. Como una DLL que ofrece una comprobación a prueba de idiotas. Ya sabes, esos argumentos: “Si el usuario proporciona NULL, no quiere que su aplicación falle”. Qué no argumento. Es el usuario el que transfiere datos falsos en primer lugar; es una elección explícita y nada más que eso. Si uno siente que eso es calidad, bueno … Prefiero lógica sólida y rendimiento sobre tales cosas. Además, se supone que un progtwigdor debe saber lo que está haciendo. Si está operando con datos no válidos para el scope particular, entonces no tiene ningún negocio llamándose progtwigdor. No veo ninguna razón para rebajar el rendimiento, boost el consumo de energía y boost el tamaño del binario, lo que a su vez afecta el almacenamiento en caché de las instrucciones y la predicción de bifurcación de mis productos para admitir a dichos usuarios.

Hay una distinción entre lo que llamaría responsabilidad legal y moral en este caso. Como analogía, supongamos que ves a un hombre con poca visión caminando hacia el borde de un acantilado, alegremente ajeno a su existencia. En lo que respecta a su responsabilidad legal, en general no sería posible enjuiciarlo exitosamente si no lo advierte y él sigue caminando, se cae del acantilado y muere. Por otro lado, tuviste la oportunidad de advertirlo: estabas en posición de salvar su vida, y deliberadamente elegiste no hacerlo. La persona promedio tiende a considerar tal comportamiento con desprecio, a juzgar por su responsabilidad moral de hacer lo correcto.

¿Cómo se aplica esto a la pregunta en cuestión? Simple: el destinatario no es “legalmente” responsable de las acciones del llamador, estúpido o no, como pasar entradas no válidas. Por otro lado, cuando las cosas se ponen feas y se observa que un simple control dentro de su función podría haber salvado a la persona que llama de su propia estupidez, terminará compartiendo parte de la responsabilidad moral de lo que ha sucedido.

Por supuesto, hay una compensación aquí, dependiendo de cuánto le cueste realmente el cheque. Volviendo a la analogía, supongamos que descubriste que el mismo extraño avanzaba lentamente hacia un acantilado al otro lado del mundo, y que gastando los ahorros de tu vida para volar allí y advertirle, podrías salvarlo. Muy pocas personas lo juzgarán con total dureza si, en esta situación particular, usted no lo hiciera (supongamos que el teléfono no ha sido inventado, a los efectos de esta analogía). Sin embargo, en términos de encoding, si el cheque es tan simple como verificar NULL , será negligente si no lo hace, incluso si la culpa “real” en la situación recae en la persona que llama.

Un efecto colateral de ese enfoque es que cuando su biblioteca falla en respuesta a que se le pase un argumento inválido, tenderá a echarle la culpa.

No hay mejor ejemplo de esto que el sistema operativo de Windows. Inicialmente, el enfoque de Microsoft era eliminar muchas pruebas de argumentos falsos. El resultado fue un sistema operativo que era más eficiente.

Sin embargo, la realidad es que los argumentos inválidos se pasan todo el tiempo. Desde progtwigdores que no están a la altura del rapé, o simplemente utilizando valores devueltos por otras funciones, no se esperaba que fueran NULL. Ahora, Windows realiza más validación y es menos eficiente como resultado.

Si desea permitir que sus rutinas se bloqueen, entonces no pruebe los parámetros inválidos.

La sobrecarga del tiempo de desarrollo + el rendimiento del tiempo de ejecución tiene un compromiso con la solidez de la API que está diseñando.

Si la API que está publicando tiene que ejecutarse dentro del proceso de la rutina de llamada, NO DEBERÍA comprobar si hay argumentos NULOS o inválidos. En este escenario, si falla, el progtwig cliente se bloquea y el desarrollador que usa su API debería reparar sus formas.

Sin embargo, si proporciona un runtime / framework que ejecutará el progtwig del cliente dentro de él (p. Ej., Está escribiendo una máquina virtual o un middleware que puede alojar el código o un sistema operativo), definitivamente debe verificar la corrección del argumentos pasados. No quieres que tu progtwig sea culpado por los errores de un complemento.