@dmckee
Bueno, que no quepa dentro de un comentario, pero aquí es la cosa:
En primer lugar, se escribe un analizador estático correcta. "Correcto", en este contexto, significa que no permanecerá en silencio si hay algo dudoso sobre el código analizado, por lo que en esta etapa confunde alegremente conductas indefinidas y no especificadas. Ambos son malos e inaceptables en el código crítico, y adviertes, con razón, para los dos.
Pero solo quiere advertir una vez por un posible error, y también sabe que su analizador será juzgado en puntos de referencia en términos de "precisión" y "recuperación" en comparación con otros analizadores posiblemente no correctos, por lo no debe advertir dos veces acerca de un mismo problema ... Sea una alarma verdadera o falsa (usted no sabe cuál. Nunca se sabe cuál, de lo contrario sería demasiado fácil).
Así que quieres emitir una única advertencia para
*p = x;
y = *p;
Porque tan pronto como p
es un puntero válido en la primera declaración, se puede suponer que un puntero válido en la segunda declaración. Y no inferir esto reducirá su puntaje en la métrica de precisión.
Así que le enseña a su analizador que asume que p
es un puntero válido tan pronto como lo haya advertido la primera vez en el código anterior, para que no lo advierta la segunda vez. De manera más general, aprendes a ignorar los valores (y las rutas de ejecución) que corresponden a algo que ya has advertido.
Luego, se da cuenta de que no muchas personas escriben código crítico, por lo que realiza otros análisis livianos para el resto de ellos, según los resultados del análisis inicial correcto. Digamos, un cortador de programas en C.
Y les dice "ellos": no tiene que verificar todas las alarmas (posiblemente falsas) emitidas por el primer análisis. El programa rebanado se comporta igual que el programa original, siempre que ninguno de ellos se active. El slicer produce programas que son equivalentes para el criterio de segmentación para rutas de ejecución "definidas".
Y los usuarios ignoran alegremente las alarmas y usan el slicer.
Y luego se da cuenta de que tal vez haya un malentendido. Por ejemplo, la mayoría de las implementaciones de memmove
(ya sabes, la que maneja bloques superpuestos) en realidad invocan un comportamiento no especificado cuando se llaman con punteros que no apuntan al mismo bloque (comparando direcciones que no apuntan al mismo bloque). Y su analizador ignora ambas rutas de ejecución, porque ambas no están especificadas, pero en realidad ambas rutas de ejecución son equivalentes y todo está bien.
Por lo tanto, no debe haber ningún malentendido sobre el significado de las alarmas, y si se intenta ignorarlas, solo deben excluirse comportamientos indefinidos inequívocos.
Y así es como terminas con un gran interés en distinguir entre el comportamiento no especificado y el comportamiento indefinido. Nadie puede culparte por ignorar lo último. Pero los programadores escribirán lo primero sin siquiera pensarlo, y cuando diga que su rebanadora excluye los "comportamientos incorrectos" del programa, no lo sentirán como ellos.
Y este es el final de una historia que definitivamente no cabe en un comentario. Disculpas a cualquiera que haya leído eso.
+1 buen tema .. –
6.5.2.2 párrafo 12 contiene el ejemplo '(* pf [f1()]) (f2(), f3() + f4())'. Si solo dijera que los efectos colaterales en 'f3' y' f4' interferían, tendría mi respuesta, pero se enfoca más en el hecho de que todos los efectos secundarios están terminados antes de '(* pf [f1()]) 'se llama. –
¿Realmente importa cuál? Cualquiera de los dos significa que no puede confiar en un comportamiento conocido que funciona en FooOS con la versión X.Y.ZpW de BarCC si cambia Foo, Bar, X, Y, Z o W. Lo mejor que puedes esperar es consistencia siempre y cuando te apegues al entorno rígidamente especificado. – dmckee