Primero, notaré que aunque solo mencione "C" aquí, lo mismo se aplica igualmente a C ++ también.
El comentario que mencionaba a Godel fue en parte (pero solo en parte) acertado.
Cuando te pones a ello, el comportamiento indefinido en los estándares C es en gran medida solo señalar el límite entre lo que el estándar intenta definir y lo que no.
Los teoremas de Godel (hay dos) básicamente dicen que es imposible definir un sistema matemático que pueda probarse (por sus propias reglas) que sea completo y consistente. Puede hacer sus reglas para que estén completas (el caso con el que trató fueron las reglas "normales" para los números naturales), o puede hacer posible demostrar su consistencia, pero no puede tener ambas.
En el caso de algo como C, eso no se aplica directamente; en su mayor parte, la "capacidad de prueba" de la integridad o consistencia del sistema no es una alta prioridad para la mayoría de los diseñadores de idiomas. Al mismo tiempo, sí, probablemente fueron influenciados (al menos en cierto grado) al saber que es probablemente imposible definir un sistema "perfecto", uno que sea demostrablemente completo y consistente. Saber que tal cosa es imposible puede haber hecho un poco más fácil dar un paso atrás, respirar un poco y decidir sobre los límites de lo que tratarían de definir.
A riesgo de (una vez más) ser acusado de arrogancia, caracterizaría el estándar C como gobernado (en parte) por dos ideas básicas:
- El lenguaje debe admitir una variedad de hardware lo más amplia posible (idealmente, todo el hardware "sano" hasta un límite inferior razonable).
- El lenguaje debe admitir la escritura de la mayor variedad de software posible para el entorno dado.
El primero significa que si alguien define una nueva CPU, debería ser posible proporcionar una implementación buena, sólida y utilizable de C para eso, siempre y cuando el diseño esté al menos razonablemente cerca de algunas pautas simples, básicamente, si sigue algo en el orden general del modelo de Von Neumann y proporciona al menos una cantidad mínima de memoria razonable, que debería ser suficiente para permitir una implementación en C. Para una implementación "alojada" (una que se ejecuta en un sistema operativo), debe admitir alguna noción que corresponda razonablemente a los archivos, y tener un conjunto de caracteres con un cierto conjunto mínimo de caracteres (se requieren 91).
El segundo significa que debería ser posible escribir código que manipule el hardware directamente, por lo que puede escribir cosas como cargadores de arranque, sistemas operativos, software integrado que se ejecuta sin ningún sistema operativo, etc. En última instancia, hay algunos límites a este respecto, por lo que casi cualquier Es probable que el sistema operativo práctico, el gestor de arranque, etc., contenga al menos un poco de código escrito en lenguaje ensamblador. Del mismo modo, es probable que incluso un pequeño sistema integrado incluya al menos algún tipo de rutinas de biblioteca preescritas para dar acceso a los dispositivos en el sistema host. Aunque es difícil definir un límite preciso, la intención es que la dependencia de dicho código se mantenga al mínimo.
El comportamiento indefinido en el lenguaje está impulsado en gran medida por la intención del lenguaje de admitir estas capacidades. Por ejemplo, el idioma le permite convertir un entero arbitrario en un puntero y acceder a lo que sea que esté en esa dirección. El estándar no intenta decir qué sucederá cuando lo haga (por ejemplo, incluso leer desde algunas direcciones puede tener efectos visibles desde el exterior). Al mismo tiempo, no intenta evitar que haga tales cosas, porque necesita algunos tipos de software que se supone que puede escribir en C.
Existe también un comportamiento indefinido impulsado por otros elementos de diseño. Por ejemplo, otra intención de C es admitir una compilación separada. Esto significa (por ejemplo) que se pretende que pueda "vincular" piezas utilizando un vinculador que sigue aproximadamente lo que la mayoría de nosotros vemos como el modelo habitual de un vinculador. En particular, debería ser posible combinar módulos compilados por separado en un programa completo sin el conocimiento de la semántica del lenguaje.
Hay otro tipo de comportamiento indefinido (que es mucho más común en C ++ que C), que está presente simplemente debido a los límites en la tecnología del compilador: cosas que básicamente sabemos que son errores, y probablemente le gustaría que el compilador diagnostique como errores, pero dados los límites actuales en la tecnología de compilación, es dudoso que puedan diagnosticarse en todas las circunstancias. Muchos de estos están impulsados por otros requisitos, como la compilación por separado, por lo que se trata principalmente de equilibrar los requisitos en conflicto, en cuyo caso el comité generalmente ha optado por apoyar mayores capacidades, incluso si eso significa la falta de diagnóstico de algunos posibles problemas, en lugar de limitar las capacidades para garantizar que se diagnostiquen todos los posibles problemas.
Estas diferencias en la intención impulsan la mayoría de las diferencias entre C y algo como Java o los sistemas basados en CLI de Microsoft. Estos últimos están bastante explícitamente limitados a trabajar con un conjunto de hardware mucho más limitado, o requieren que el software emule el hardware más específico al que se dirigen. También tienen la intención específica de evitar cualquier manipulación directa de hardware, en su lugar requieren que use algo como JNI o P / Invoke (y código escrito en algo como C) para incluso hacer tal intento.
Volviendo a los teoremas de Godel por un momento, podemos trazar una especie de paralelo: Java y CLI han optado por la alternativa "internamente consistente", mientras que C ha optado por la alternativa "completa". Por supuesto, esto es una analogía muy áspera - Dudo que alguien está intentando una prueba formal de cualquiera de consistencia interna o la integridad en ambos casos. No obstante, la noción general encaja bastante estrechamente con las elecciones que han tomado.