¿Por qué se permite un comportamiento indefinido en C?

7 minutos de lectura

¿Por que se permite un comportamiento indefinido en C
solo bourv

He estado jugando tratando de aprender C últimamente. Viniendo de Java, me sorprendió que puedas realizar ciertas operaciones declaradas como “indefinidas”.

Esto me parece extremadamente inseguro. Entiendo que es responsabilidad del programador no realizar operaciones indefinidas, pero ¿por qué se permite siquiera comenzar? ¿Por qué el compilador no detecta, por ejemplo, índices de matriz fuera de los límites o incluso punteros colgantes? Simplemente terminas accediendo a bloques de memoria a los que nunca deberías acceder, sin (aparentemente) una buena razón.

Como comparación, Java hace muy seguro no lo haces cualquier cosa estúpido, lanzando Excepciones como pan caliente.

Seguramente debe haber una razón por la que esto está permitido. ¿Qué es?

RESPUESTA: A mi entender, la razón principal es el rendimiento. Además, Java tiene comportamientos indefinidos, aunque no etiquetados como tales.

EDITAR: pregunta restringida a C

  • Hay una penalización en el rendimiento al verificar los límites de cada acceso a la matriz.

    –Paul Boddington

    10 de abril de 2016 a las 0:23

  • La respuesta a esta pregunta es: porque esto es C++.

    – Sam Varshavchik

    10 de abril de 2016 a las 0:23

  • El compilador no puede detectar todo el comportamiento indefinido (es el equivalente al problema de la detención). Los compiladores intentarán detectarlo cuando puedan y advertirle al respecto, lo que ayuda, pero C y C++ le permiten dispararse en el pie si lo desea.

    – Tallos de maiz

    10 de abril de 2016 a las 0:25

  • Técnicamente, Java también tiene su parte de lo que (en C y C++) se describiría como comportamientos indefinidos, no especificados, etc. La diferencia clave es que no están documentados como tales. Esto incluye (a) cualquier cosa que tenga que ver con subprocesos, (b) el comportamiento del recolector de basura, (c) ciertos aspectos de la inicialización, duración y finalización del objeto, (d) una serie de comportamientos de las bibliotecas de interfaz de usuario (Swing, AWT) , (e) un montón de cosas relacionadas con el rendimiento, la latencia… la lista continúa.

    – Pedro

    10 de abril de 2016 a las 0:47


  • @Peter Continúe mientras me alegra el día 🙂 Pero, para trabajar a partir de lo que ya ha dicho: la incertidumbre de GC hace que Java inadecuado para cualquier software de misión crítica (por ejemplo, el control de una nave espacial o una máquina corazón-pulmón) que requiere un previsible tiempo de respuesta en tiempo real. (p. ej.) Se debe hacer algo en preciso intervalos de tiempo a 100 veces/segundo. Pero, esto podría retrasarse. [for several seconds sometimes] porque el GC “simplemente decidió” entrar en el momento equivocado.

    –Craig Estey

    10 de abril de 2016 a las 1:08

1647635288 217 ¿Por que se permite un comportamiento indefinido en C
Serguéi Kalinichenko

El comportamiento indefinido no está permitido, simplemente no lo detecta el compilador.

La compensación aquí es entre la velocidad y la seguridad. Muchos tipos de comportamiento indefinido podrían evitarse a expensas de unos pocos ciclos de CPU adicionales.

Por ejemplo, puede evitar que UB ocurra cuando lee de la memoria que ha sido asignada pero no inicializada haciendo que el código compilado escriba ceros en ella. Esto, sin embargo, le cuesta una escritura adicional completa en una memoria, lo cual es completamente innecesario.

De manera similar, uno podría evitar leer/escribir más allá del final de una matriz al verificar sus límites dentro [] operador. Sin embargo, esto le costaría algunos ciclos de CPU adicionales en cada acceso al arreglo.

Los diseñadores de C++ decidieron que es mejor tener velocidad y permitir UB potencial que obligar a todos a pagar por lo que no necesitan. Este enfoque, sin embargo, es incompatible con el requisito de “escribir una vez, ejecutar en cualquier lugar” de Java, por lo que los diseñadores del lenguaje Java insistieron en un comportamiento completamente definido en casi todas las situaciones.

  • ¿No es un poco fuerte el comportamiento totalmente definido en todas las situaciones?

    –Paul Boddington

    10 de abril de 2016 a las 0:32

  • @PaulBoddington Aparte de JNI, el estándar Java es un “fanático del control”: el compilador produce un error o obtiene el comportamiento descrito en el estándar. Al menos eso es lo que esperan los escritores estándar.

    – Serguéi Kalinichenko

    10 de abril de 2016 a las 0:41

  • Eso es cierto, y realmente me gusta Java por eso, pero hay un comportamiento indefinido, por ejemplo, no hay forma de predecir cuándo el tamaño de un WeakHashMap podría cambiar.

    –Paul Boddington

    10 de abril de 2016 a las 0:44

  • Lo que dices no es cierto en general. Considere distinguir entre código ejecutado en compilación y en tiempo de ejecución. Los compiladores modernos son mucho más inteligentes de lo que piensas.

    – Una matriz de funciones

    10 de abril de 2016 a las 0:58

  • @FISOCPP ¿Podría señalar el lenguaje en mi respuesta que lo llevó a la conclusión de su comentario, por ejemplo, que los compiladores modernos no son lo suficientemente inteligentes? Mi respuesta es sobre compiladores renuencia para prevenir UB, no su incapacidad para hacerlo

    – Serguéi Kalinichenko

    10 de abril de 2016 a las 1:36

Originalmente, la mayoría de las formas de comportamiento indefinido representaban cosas que algunas implementaciones podían atrapar, pero otras no. Porque los autores del Estándar no tenían forma de predecir todo lo que una plataforma podría hacer en caso de una trampa (incluida, literalmente, la posibilidad de que un sistema haga sonar una alarma y se bloquee hasta que un operador elimine manualmente la falla) , las consecuencias de las trampas caían fuera de la jurisdicción del Estándar C y, por lo tanto, casi todas las acciones para las que alguna plataforma podría causar una trampa son, desde el punto de vista del Estándar, consideradas “Comportamiento indefinido”.

Eso no debe interpretarse como que implica que los autores del Estándar no creían que las implementaciones deberían tratar de comportarse de manera sensata para tales cosas cuando sea práctico. Los autores del estándar C89 señalaron, por ejemplo, que la mayoría de los sistemas actuales de esa era definirían el comportamiento para:

/* Assume USmall is half the size of "int" */
unsigned mult(USmall x, USmall y) { return x*y; }

que sería en todos los casos, incluidos aquellos en los que el producto matemático de xey estaba entre INT_MAX+1 y UINT_MAXser equivalente a (unsigned)x*y;. No veo ninguna razón para creer que no hubieran esperado que esa tendencia continuara.

Desafortunadamente, una nueva filosofía se ha puesto de moda, basada en el punto de vista revisionista de que los escritores de compiladores solo admitían comportamientos útiles en los casos que no exigía el estándar porque eran demasiado poco sofisticados para hacer cualquier otra cosa. En gcc, por ejemplo, al usar el nivel de optimización 2 pero ninguna otra opción no predeterminada, la rutina “mult” anterior a veces generará código falso en los casos en que el producto estaría entre 0x80000000u y 0xFFFFFFFFu, incluso cuando se ejecuta en plataformas donde tales cálculos serían históricamente han trabajado. Esto supuestamente se hace en nombre de la “optimización”; Sería interesante saber cuántas de las “optimizaciones” que estas técnicas terminan realizando son realmente útiles y no podrían haberse logrado a través de medios más seguros.

Históricamente, Undefined Behavior era una licencia para que un compilador de C expusiera el comportamiento de la plataforma subyacente; en los casos en que el comportamiento de la plataforma subyacente se ajustaba a las necesidades del programador, esto permitía que los requisitos del programador se expresaran en código de máquina de manera más eficiente que si todo tuviera que hacerse de la forma definida por el Estándar. Últimamente, sin embargo, se ha interpretado como una licencia para que los compiladores implementen comportamientos que no solo no guardan relación con nada en la plataforma subyacente ni con las expectativas plausibles del programador, sino que ni siquiera están sujetos a las leyes del tiempo y la causalidad.

Java tiene un entorno de tiempo de ejecución para cuidar de usted. Es por eso que se lanza una excepción cuando se sale de los límites: es algo que no se puede resolver en el momento de la compilación.

Hay límites de tiempo de ejecución comprobando en C++ cuando se usa el método at() para un vector. Es lo que distingue a at() de la []operador

¿Ha sido útil esta solución?

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para mostrarte publicidad relacionada con sus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Al hacer clic en el botón Aceptar, acepta el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Configurar y más información
Privacidad