¿Qué significa hacer una “ verificación nula ” en C o C ++?

He estado aprendiendo C ++ y me cuesta entender null. En particular, los tutoriales que he leído mencionan hacer una «verificación nula», pero no estoy seguro de lo que eso significa o por qué es necesario.

  • ¿Qué es exactamente nulo?
  • ¿Qué significa «verificar nulo»?
  • ¿Necesito siempre verificar nulo?

Cualquier ejemplo de código sería muy apreciado.

Comentarios

Responder

En C y C ++, los punteros son intrínsecamente inseguros, es decir, cuando se elimina la referencia a un puntero, es su propia responsabilidad asegurarse de que apunte a un lugar válido; esto es parte de lo que se trata la «administración manual de memoria» (a diferencia de los esquemas de administración automática de memoria implementados en lenguajes como Java , PHP o el tiempo de ejecución .NET, que no le permitirá crear referencias inválidas sin un esfuerzo considerable).

Una solución común que detecta muchos errores es establecer todos los punteros que no apuntan a nada como NULL (o, en C ++ correcto, 0) y verificando eso antes de acceder al puntero. Específicamente, es una práctica común inicializar todos los punteros a NULL (a menos que ya tenga algo a lo que apuntar cuando los declare), y establecerlos en NULL cuando delete o free() ellos (a menos que salgan del alcance inmediatamente después de eso). Ejemplo (en C, pero también en C ++ válido):

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

Una versión mejor:

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

Sin la verificación nula, pasar un puntero NULL a esta función provocará un error de segmento, y no hay nada que pueda hacer: el sistema operativo simplemente matará su proceso y tal vez core-dump o mostrará un cuadro de diálogo de informe de bloqueo. Con la verificación nula en su lugar, puede realizar el manejo adecuado de errores y recuperarse correctamente: corrija el problema usted mismo, cancele la operación actual, escriba una entrada de registro, notifique al usuario, lo que sea apropiado.

Comentarios

  • @MrLister, ¿qué quieres decir con que las comprobaciones nulas no ‘ funcionan en C ++? Solo tiene que inicializar el puntero en nulo cuando lo declare.
  • Lo que quiero decir es que debe recordar configurar el puntero en NULL o ganó ‘ t trabajo. Y si recuerdas, en otras palabras, si sabes que el puntero es NULL, no ‘ tendrás la necesidad de llamar a fill_foo de todos modos. fill_foo comprueba si el puntero tiene un valor, no si el puntero tiene un valor válido . En C ++, no se garantiza que los punteros sean NULL o que tengan un valor válido.
  • Un assert () sería una mejor solución aquí. No tiene sentido ‘ intentar » estar a salvo «. Si se pasó NULL, es ‘ obviamente incorrecto, así que ¿por qué no bloquearse explícitamente para que el programador esté completamente al tanto? (Y en producción, no ‘ t importa, porque ‘ has probado que nadie llamará a fill_foo () con NULL, ¿verdad? En serio, ‘ no es tan difícil.)
  • No ‘ te olvides para mencionar que una versión aún mejor de esta función debería usar referencias en lugar de punteros, haciendo que la verificación NULL sea obsoleta.
  • De esto no se trata la administración manual de memoria, y un programa administrado también explotará, ( o generar una excepción al menos, como lo haría un programa nativo en la mayoría de los idiomas,) si intenta eliminar la referencia a una referencia nula.

Respuesta

Las otras respuestas cubrían prácticamente su pregunta exacta. Se realiza una verificación nula para asegurarse de que el puntero que recibió realmente apunte a una instancia válida de un tipo (objetos, primitivas, etc.).

Voy a agregar mi propio consejo aquí, Sin embargo. Evite las comprobaciones nulas. 🙂 Las comprobaciones nulas (y otras formas de programación defensiva) desordenan el código y lo hacen más propenso a errores que otras técnicas de manejo de errores.

Mi técnica favorita cuando se trata de punteros de objeto es utilizar el patrón de objeto nulo . Eso significa devolver un (puntero – o mejor aún, una referencia a una) matriz o lista vacía en lugar de nulo, o devolviendo una cadena vacía («») en lugar de nula, o incluso la cadena «0» (o algo equivalente a «nada» en el contexto) donde espera que se analice en un entero.

Como beneficio adicional, aquí hay algo que quizás no sabías sobre el puntero nulo, que fue (por primera vez formalmente) implementado por CAR Hoare para el lenguaje Algol W en 1965.

Lo llamo mi error de mil millones de dólares. Fue la invención de la referencia nula en 1965. En ese momento, estaba diseñando el primer sistema de tipos completo para referencias en un objeto. lenguaje orientado (ALGOL W). Mi objetivo era asegurar que todo uso de referencias fuera absolutamente seguro, con verificación realizada automáticamente por el compilador. Pero no pude resistir la tentación de poner una referencia nula, simplemente porque era tan fácil de implementar. Esto ha provocado innumerables errores, vulnerabilidades y caídas del sistema, que probablemente han causado mil millones de dólares en dolor y daños en los últimos cuarenta años.

Comentarios

  • Objeto nulo es incluso peor que tener un puntero nulo. Si un algoritmo X requiere datos Y que usted no tiene, entonces eso es un error en su programa , que simplemente está ocultando pretendiendo que sí.
  • Depende de el contexto, y de cualquier manera, la prueba de » presencia de datos » supera la prueba de nulo en mi libro. Según mi experiencia, si un algoritmo funciona en, digamos, una lista, y la lista está vacía, entonces el algoritmo simplemente no tiene nada que hacer y lo logra simplemente usando declaraciones de control estándar como for / foreach.
  • Si el algoritmo no tiene nada que hacer, ¿por qué lo llama? Y la razón por la que podría haber querido llamarlo en primer lugar es porque hace algo importante .
  • @DeadMG Porque los programas se tratan de entradas, y en el mundo real, a diferencia de asignaciones de tareas, la entrada puede ser irrelevante (por ejemplo, vacía). El código todavía se llama de cualquier manera. Tiene dos opciones: o verifica la relevancia (o el vacío), o diseña sus algoritmos para que se lean y funcionen bien sin verificar explícitamente la relevancia usando declaraciones condicionales.
  • Vine aquí para hacer casi el mismo comentario, así que le di mi voto en su lugar. Sin embargo, también agregaría que esto es representativo de un problema mayor de objetos zombies : cada vez que tiene objetos con inicialización (o destrucción) de múltiples etapas que no están completamente vivos pero no del todo muertos. Cuando vea código » seguro » en idiomas sin finalización determinista que haya agregado comprobaciones en cada función para ver si el objeto se ha eliminado, es este problema general el que le hace ‘ la cabeza. Nunca debe if-null, debe trabajar con estados que tengan los objetos que necesitan durante su vida.

Respuesta

El valor del puntero nulo representa un «ningún lugar» bien definido; es un valor de puntero inválido que garantiza una comparación desigual con cualquier otro valor de puntero. Intentar eliminar la referencia a un puntero nulo da como resultado un comportamiento indefinido y, por lo general, dará lugar a un error de tiempo de ejecución, por lo que debe asegurarse de que un puntero no sea NULL antes de intentar eliminar la referencia. Varias funciones de biblioteca de C y C ++ devolverán un puntero nulo para indicar una condición de error. Por ejemplo, la función de biblioteca malloc devolverá un valor de puntero nulo si no puede asignar el número de bytes que se han solicitado, e intentar acceder a la memoria a través de ese puntero (generalmente) conducirá a un error de tiempo de ejecución:

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

Por lo tanto, debemos asegurarnos de que la llamada malloc se haya realizado correctamente comprobando el valor de p contra NULL:

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

Ahora, agárrate a tus calcetines un minuto, esto va a obtener un poco irregular.

Hay un puntero nulo valor y un puntero nulo constante , y los dos no son necesariamente iguales. El valor del puntero nulo es cualquier valor que utilice la arquitectura subyacente para representar «ninguna parte». Este valor puede ser 0x00000000, 0xFFFFFFFF, 0xDEADBEEF o algo completamente diferente. No asuma que el valor del puntero nulo es siempre 0.

La constante del puntero nulo, OTOH, es siempre una expresión integral con valor 0. En lo que respecta a su código fuente , 0 (o cualquier expresión integral que se evalúe como 0) representa un puntero nulo. Tanto C como C ++ definen la macro NULL como la constante de puntero nulo. Cuando se compila su código, el puntero nulo constante será reemplazado por el puntero nulo valor apropiado en el código de máquina generado.

Además, tenga en cuenta que NULL es solo uno de los muchos posibles valores de puntero inválidos ; si declara una variable de puntero automático sin inicializarla explícitamente, como

int *p; 

el valor almacenado inicialmente en la variable es indeterminado , y puede no corresponder a una dirección de memoria válida o accesible. Desafortunadamente, no hay forma (portátil) de saber si un valor de puntero no NULL es válido o no antes de intentar usarlo. Por lo tanto, si está tratando con punteros, generalmente es una buena idea inicializarlos explícitamente a NULL cuando los declare, y ponerlos en NULL cuando no estén apuntando activamente a nada.

Tenga en cuenta que esto es más un problema en C que en C ++; C ++ idiomático no debería usar tanto punteros.

Respuesta

Hay un par de métodos, todos esencialmente hacen lo mismo cosa.

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

verificación nula (verifique si el puntero es nulo), versión A

 if( foo == NULL) 

verificación nula, versión B

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

verificación nula, versión C

 if( foo == 0 ) 

De las tres, prefiero usar la primera verificación, ya que le dice explícitamente a los futuros desarrolladores lo que estaba tratando de verificar Y deja en claro que esperaba que foo fuera un puntero.

Respuesta

No es así. La única razón para usar un puntero en C ++ es porque desea explícitamente la presencia de punteros nulos; de lo contrario, puede tomar una referencia, que es semánticamente más fácil de usar y garantiza que no sea nulo.

Comentarios

  • @James: ‘ nuevo ‘ en modo kernel?
  • @James: Una implementación de C ++ que representa las capacidades que una mayoría significativa de C ++ los codificadores disfrutan. Eso incluye todas las funciones del lenguaje C ++ 03 (excepto export) y todas las funciones de la biblioteca C ++ 03 y TR1 y una buena parte de C ++ 11.
  • Yo desearía que la gente no ‘ dijera que » las referencias garantizan que no sean nulas. » No ‘ t. Es tan fácil generar una referencia nula como un puntero nulo, y se propagan de la misma manera.
  • @Stargazer: La pregunta es 100% redundante cuando solo usa las herramientas de la manera en que los diseñadores de lenguaje y los buenos la práctica sugiere que debería hacerlo.
  • @DeadMG, no ‘ no importa si es redundante. No ‘ no respondió la pregunta . Lo ‘ lo diré de nuevo: -1.

Responder

Si no marca el valor NULL, especialmente, si se trata de un puntero a una estructura, es posible que haya encontrado una vulnerabilidad de seguridad: desreferencia del puntero NULL. La desreferencia del puntero NULL puede conducir a otras vulnerabilidades de seguridad graves, como el desbordamiento del búfer condición de carrera … que puede permitir que el atacante tome el control de su computadora.

Muchos proveedores de software como Microsoft, Oracle, Adobe, Apple … lanzan un parche de software para corregir estas vulnerabilidades de seguridad. Creo que debería comprobar el valor NULO de cada puntero 🙂

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *