C LanguageComportamiento indefinido


Introducción

En C, algunas expresiones producen un comportamiento indefinido . El estándar elige explícitamente no definir cómo debe comportarse un compilador si encuentra tal expresión. Como resultado, un compilador es libre de hacer lo que crea conveniente y puede producir resultados útiles, resultados inesperados o incluso fallar.

El código que invoca a UB puede funcionar según lo previsto en un sistema específico con un compilador específico, pero es probable que no funcione en otro sistema, o con un compilador diferente, una versión del compilador o la configuración del compilador.

Observaciones

¿Qué es el comportamiento indefinido (UB)?

Comportamiento indefinido es un término usado en el estándar C. El estándar C11 (ISO / IEC 9899: 2011) define el término comportamiento indefinido como

comportamiento, en el uso de un constructo de programa no portátil o erróneo o de datos erróneos, para los cuales esta Norma Internacional no impone requisitos

¿Qué pasa si hay UB en mi código?

Estos son los resultados que pueden ocurrir debido a un comportamiento indefinido según el estándar:

NOTA El posible comportamiento indefinido abarca desde ignorar la situación completamente con resultados impredecibles, hasta comportarse durante la traducción o la ejecución del programa de una manera documentada característica del entorno (con o sin la emisión de un mensaje de diagnóstico), hasta terminar una traducción o ejecución (con la emisión de un mensaje de diagnóstico).

La siguiente cita se usa a menudo para describir (de manera menos formal) los resultados que suceden a partir de un comportamiento indefinido:

"Cuando el compilador se encuentra con [una construcción indefinida dada] es legal que haga que los demonios salgan volando de tu nariz" (la implicación es que el compilador puede elegir cualquier manera arbitrariamente extraña de interpretar el código sin violar el estándar ANSI C)

¿Por qué existe UB?

Si es tan malo, ¿por qué no lo definieron o lo hicieron definido por la implementación?

El comportamiento indefinido permite más oportunidades de optimización; El compilador puede suponer justificadamente que cualquier código no contiene un comportamiento indefinido, lo que puede permitirle evitar verificaciones en tiempo de ejecución y realizar optimizaciones cuya validez sería costosa o imposible de demostrar de otro modo.

¿Por qué es difícil localizar a UB?

Hay al menos dos razones por las que un comportamiento indefinido crea errores que son difíciles de detectar:

  • No se requiere que el compilador, y generalmente no puede advertirle, sobre un comportamiento indefinido. De hecho, exigir que lo haga iría directamente en contra de la razón de la existencia de un comportamiento indefinido.
  • Es posible que los resultados impredecibles no comiencen a desplegarse en el punto exacto de la operación donde se produce la construcción cuyo comportamiento no está definido; El comportamiento no definido mancha toda la ejecución y sus efectos pueden ocurrir en cualquier momento: durante, después o incluso antes de la construcción indefinida.

Considere la posibilidad de no hacer referencia al puntero nulo: el compilador no está obligado a diagnosticar la falta de referencia al puntero nulo, e incluso no podría hacerlo, ya que, en el tiempo de ejecución, cualquier puntero pasado a una función o en una variable global podría ser nulo. Y cuando se produce la anulación de referencia a un puntero nulo, la norma no exige que el programa deba bloquearse. Más bien, el programa podría fallar más temprano o más tarde o no fallar; Incluso podría comportarse como si el puntero nulo apuntara a un objeto válido, y se comportara completamente normalmente, solo para bloquearse en otras circunstancias.

En el caso de la desreferenciación de puntero nulo, el lenguaje C difiere de los lenguajes administrados como Java o C #, donde se define el comportamiento de la desreferencia de puntero nulo: se lanza una excepción, en el momento exacto ( NullPointerException en Java, NullReferenceException en C #) Por lo tanto, aquellos que vienen de Java o C # pueden creer incorrectamente que, en tal caso, un programa en C debe fallar, con o sin la emisión de un mensaje de diagnóstico .

Información Adicional

Hay varias situaciones de este tipo que deben distinguirse claramente:

  • Comportamiento explícitamente indefinido, ahí es donde el estándar C le dice explícitamente que está fuera de los límites.
  • Comportamiento implícito indefinido, donde simplemente no hay texto en el estándar que prevea un comportamiento para la situación en la que trajo su programa.

También tenga en cuenta que en muchos lugares el comportamiento de ciertas construcciones está deliberadamente indefinido por el estándar C para dejar espacio para que los implementadores de compiladores y bibliotecas elaboren sus propias definiciones. Un buen ejemplo son las señales y los manejadores de señales, donde las extensiones a C, como el estándar del sistema operativo POSIX, definen reglas mucho más elaboradas. En tales casos, solo tiene que consultar la documentación de su plataforma; El estándar C no te puede decir nada.

También tenga en cuenta que si ocurre un comportamiento indefinido en el programa, esto no significa que solo el punto donde ocurrió un comportamiento indefinido sea problemático, un programa completo deja de tener sentido.

Debido a estas preocupaciones, es importante (sobre todo porque los compiladores no siempre nos advierten acerca de UB) para que la persona programada en C esté al menos familiarizada con el tipo de cosas que desencadenan un comportamiento indefinido.

Cabe señalar que hay algunas herramientas (por ejemplo, herramientas de análisis estático, como PC-Lint) que ayudan a detectar un comportamiento indefinido, pero nuevamente, no pueden detectar todas las apariciones de un comportamiento indefinido.

Comportamiento indefinido Ejemplos relacionados