c++ - Definiendo el comportamiento indefinido
undefined-behavior (4)
Dos intérpretes de C que detectan una gran clase de comportamientos indefinidos para un gran subconjunto de C secuencial son el análisis de valores de KCC y Frama-C . Ambos se utilizan para asegurarse de que los programas de C aleatorios aleatorios generados automáticamente y reducidos automáticamente sean adecuados para report errores en los compiladores de C.
De la página web de KCC:
Uno de los objetivos principales de este trabajo es la capacidad de detectar programas indefinidos (por ejemplo, programas que leen memoria no válida).
Un tercer intérprete para un dialecto de C es el modo de intérprete de CompCert (una writeup ). Este detecta todos los comportamientos que no están definidos en el idioma de entrada del compCert CompCert certificado en C. El lenguaje de entrada de CompCert es esencialmente C, pero presenta determinados comportamientos que no están definidos en el estándar (el desbordamiento aritmético firmado se define como la computación de los resultados del complemento 2, por ejemplo).
En verdad, los tres intérpretes mencionados en esta respuesta han tenido que tomar decisiones difíciles en nombre del pragmatismo.
¿Existe alguna implementación de C ++ (y / o C) que garantice que, en cualquier momento, se invoque un comportamiento indefinido, señalará un error? Obviamente, tal implementación no podría ser tan eficiente como una implementación estándar de C ++, pero podría ser una herramienta útil de depuración / prueba.
Si tal implementación no existe, ¿existen razones prácticas que imposibiliten su implementación? ¿O es simplemente que nadie ha hecho el trabajo para implementarlo?
Edición: Para hacer esto un poco más preciso: me gustaría tener un compilador que me permita hacer la afirmación, para una ejecución determinada de un programa C ++ que se ejecutó hasta el final , que ninguna parte de esa ejecución implicó un comportamiento indefinido.
El nuevo estándar C tiene una lista interesante en el nuevo Anexo L con el título en bruto "Análisis". Habla de UB que se llama UB crítica . Esto incluye entre otros:
- Se hace referencia a un objeto fuera de su vida útil (6.2.4).
- Se utiliza un puntero para llamar a una función cuyo tipo no es compatible con el tipo referenciado
- El programa intenta modificar una cadena literal.
Todos estos son UB que son imposibles o muy difíciles de capturar, ya que generalmente no se pueden probar completamente en el momento de la compilación. Esto se debe al hecho de que un programa de C (o C ++) válido se compone de varias unidades de compilación que pueden no conocerse mucho entre sí. Por ejemplo, si un programa pasa un puntero a un literal de cadena en una función con un parámetro char*
, o incluso peor, un programa que elimina la constancia de una variable estática.
El punto central de definir algo como "comportamiento indefinido" es evitar tener que detectar esta situación en el compilador. Se define de esa manera, para que los compiladores puedan construirse para una amplia variedad de plataformas y arquitecturas, y para que el hardware y el software no tengan que tener características específicas "solo para detectar un comportamiento indefinido". Imagina que tienes un subsistema de memoria que no puede detectar si estás escribiendo en la memoria real o no. ¿Cómo detectaría el compilador o el sistema de tiempo de ejecución que acabas de hacer somepointer = rand(); *somepointer = 42;
somepointer = rand(); *somepointer = 42;
Puedes detectar ALGUNAS situaciones. Pero exigir que se detecten TODOS, dificultaría mucho la vida.
Dada la edición en la pregunta original: todavía no creo que esto sea plausible de lograr en C. Hay tanta libertad para hacer casi cualquier cosa (haciendo punteros a casi cualquier cosa, estos punteros se pueden convertir, indexar, recalcular, y todo forma de otras cosas), y será capaz de causar todo tipo de comportamiento indefinido. Aquí hay una lista de todos los comportamientos indefinidos en C: enumera 186 circunstancias diferentes de comportamientos no definidos, que van desde una barra invertida hasta el último carácter del archivo (es probable que cause un error del compilador, pero no se define como uno) a "La función de comparación llamada por la función bsearch o qsort devuelve valores de ordenamiento inconsistentemente ".
¿Cómo diablos escribe un compilador para verificar que la función pasada a bsearch o qsort está ordenando los valores de manera consistente? Por supuesto, si los datos pasados a la función de comparación son de un tipo simple, como los enteros, entonces no es tan difícil, pero si el tipo de datos es un tipo complejo como
struct {
char name[20];
char street[20];
int age;
char post_code[10];
};
¿Y el programador decide ordenar los datos según el nombre ascendente, la calle ascendente, la edad descendente y el código postal ascendente, en ese orden? Si eso es lo que quiere, pero de alguna manera el código se desordenó y la comparación del código postal arroja un resultado inconsistente, las cosas saldrán mal, pero es muy difícil inspeccionar formalmente ese caso. Hay muchos otros que son igualmente oscuros y complejos. Claro, SU código no puede ordenar nombres y direcciones, etc., pero probablemente alguien escriba algo así en algún momento u otro.
Si y no.
Estoy bastante seguro de que, por motivos prácticos, una implementación podría hacer de C ++ un lenguaje seguro, lo que significa que cada operación tiene un comportamiento bien definido. Por supuesto, esto conlleva una sobrecarga enorme y es probable que haya algunos casos en los que simplemente no es factible, como las condiciones de carrera en código de multiproceso.
¡Ahora, el problema es que esto no puede garantizar que su código esté definido en otras implementaciones! Es decir, aún podría invocar a UB. Por ejemplo, observe el siguiente código:
int a;
int* b;
int foo() {
a = 5;
b = &a;
return 0;
}
int bar() {
*b = a;
return 0;
}
int main() {
std::cout << foo() << bar() << std::endl;
}
De acuerdo con el estándar, el orden en que se llaman foo
y bar
depende de la implementación para decidir. Ahora, en una implementación segura, este orden tendría que definirse, probablemente siendo una evaluación de izquierda a derecha. El problema es que la evaluación de derecha a izquierda invoca a UB, que no se detectaría hasta que se ejecutara en una implementación insegura. La implementación segura podría simplemente compilar cada permutación del orden de evaluación o hacer un análisis estático, pero esto rápidamente se vuelve inviable y posiblemente indecidible.
Entonces, en conclusión, si existiera tal implementación, le daría una falsa sensación de seguridad.