visual studio microsoft guia español descargar community c c99 undefined-behavior sequence-points unspecified-behavior

studio - En C99, ¿f()+g() no está definido o simplemente no está especificado?



visual studio community (3)

Solía ​​pensar que en C99, incluso si los efectos secundarios de las funciones f y g interferían, y aunque la expresión f() + g() no contiene un punto de secuencia, f y g contendrían algo, por lo que el comportamiento no especificado: se llamará f () antes de g (), o g () antes de f ().

Ya no estoy tan seguro. ¿Qué sucede si el compilador alinea las funciones (que el compilador puede decidir hacer incluso si las funciones no están declaradas en inline ) y luego vuelve a ordenar las instrucciones? ¿Se puede obtener un resultado diferente de los dos anteriores? En otras palabras, ¿es este comportamiento indefinido?

Esto no se debe a que pretendo escribir este tipo de cosas, esto es elegir la mejor etiqueta para tal declaración en un analizador estático.


@dmckee

Bueno, eso no cabe en un comentario, pero aquí está la cosa:

Primero, escribes un analizador estático correcto. "Correcto", en este contexto, significa que no se mantendrá en silencio si hay algo dudoso sobre el código analizado, por lo que en esta etapa usted confunde alegremente comportamientos indefinidos y no especificados. Ambos son malos e inaceptables en el código crítico, y usted advierte, con razón, para ambos.

Pero solo desea advertir una vez por un posible error, y también sabe que su analizador se evaluará en términos de referencia en términos de "precisión" y "recuperación" en comparación con otros analizadores, posiblemente no correctos, por lo que no debe advierte dos veces sobre un mismo problema ... Sea una alarma verdadera o falsa (no sabes cuál. Nunca sabes cuál, de lo contrario sería demasiado fácil).

Así que quieres emitir una sola advertencia para

*p = x; y = *p;

Debido a que tan pronto como p es un puntero válido en la primera instrucción, se puede asumir que es un puntero válido en la segunda instrucción. Y no inferir esto disminuirá su puntaje en la métrica de precisión.

Así que le enseña a su analizador a asumir 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. Más generalmente, aprendes a ignorar los valores (y las rutas de ejecución) que corresponden a algo de lo que ya has advertido.

Entonces, te das cuenta de que no hay mucha gente que esté escribiendo código crítico, por lo que haces otros análisis ligeros para el resto de ellos, en función de los resultados del análisis inicial y correcto. Digamos, un programa de C slicer.

Y les dice a "ellos": no tiene que verificar todas las alarmas (posiblemente falsas) emitidas por el primer análisis. El programa segmentado se comporta igual que el programa original siempre que ninguno de ellos se active. El segmentador produce programas que son equivalentes para el criterio de segmentación para las rutas de ejecución "definidas".

Y los usuarios ignoran alegremente las alarmas y usan la cortadora.

Y entonces te das cuenta de que tal vez hay un malentendido. Por ejemplo, la mayoría de las implementaciones de memmove (ya sabes, la que maneja los bloques superpuestos) en realidad invoca un comportamiento no especificado cuando se llama 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 debería haber ningún malentendido sobre el significado de las alarmas, y si uno intenta ignorarlas, solo deben excluirse los comportamientos indefinidos inequívocos.

Y así es como terminas con un gran interés en distinguir entre un comportamiento no especificado y un comportamiento no definido. Nadie puede culparte por ignorar a este último. Pero los programadores escribirán el primero sin siquiera pensarlo, y cuando diga que su cortador excluye los "comportamientos incorrectos" del programa, no se sentirán como están preocupados.

Y este es el final de una historia que definitivamente no encaja en un comentario. Disculpas a cualquiera que haya leído tan lejos.


La expresión f() + g() contiene un mínimo de 4 puntos de secuencia; uno antes de la llamada a f() (después de que se hayan evaluado todos los cero de sus argumentos); uno antes de la llamada a g() (después de que se hayan evaluado todos los cero de sus argumentos); una como la llamada a f() devuelve; y una como la llamada a g() devuelve. Además, los dos puntos de secuencia asociados con f() aparecen antes o después de los dos puntos de secuencia asociados con g() . Lo que no se puede decir es en qué orden ocurrirán los puntos de la secuencia: si los puntos F se producen antes de los puntos G o viceversa.

Incluso si el compilador insertó el código, debe obedecer la regla "como si": el código debe comportarse igual que si las funciones no estuvieran intercaladas. Eso limita el alcance del daño (asumiendo un compilador que no tenga errores).

Por lo tanto, no se especifica la secuencia en la que se evalúan f() g() . Pero todo lo demás es bastante limpio.

En un comentario, supercat pregunta:

Espero que las llamadas de función en el código fuente permanezcan como puntos de secuencia, incluso si un compilador decide por sí mismo alinearlos. ¿Eso sigue siendo cierto para las funciones declaradas "en línea", o el compilador obtiene latitud extra?

Creo que se aplica la regla ''como si'' y el compilador no tenga una latitud extra para omitir puntos de secuencia porque utiliza una función explícitamente en inline . La razón principal para pensar que (ser demasiado perezoso para buscar la redacción exacta en la norma) es que el compilador tiene permitido en línea o no en línea una función de acuerdo con sus reglas, pero el comportamiento del programa no debe cambiar (a excepción de actuación).

Además, ¿qué se puede decir acerca de la secuenciación de (a(),b()) + (c(),d()) ? ¿Es posible que c() y / o d() ejecuten entre a() y b() , o que a() o b() ejecuten entre c() d() ?

  • Claramente, a se ejecuta antes de b, y c se ejecuta antes de d. Creo que es posible que c y d se ejecuten entre ayb, aunque es bastante improbable que el compilador genere el código de esa manera; De manera similar, ayb podrían ejecutarse entre cy d. Y aunque utilicé ''y'' en ''c y d'', eso podría ser un ''o'', es decir, cualquiera de estas secuencias de operación cumple con las restricciones:

    • Definitivamente permitido
    • a B C D
    • cdab
    • Posiblemente permitido (conserva a ≺ b, c ≺ d ordenar)
    • acbd
    • acdb
    • cadb
    • cabd


    Creo que cubre todas las secuencias posibles. Vea también el chat entre Jonathan Leffler y AnArrayOfFunctions . Lo esencial es que AnArrayOfFunctions no cree que las secuencias "posiblemente permitidas" estén permitidas.

Si tal cosa fuera posible, eso implicaría una diferencia significativa entre las funciones en línea y las macros.

Existen diferencias significativas entre las funciones en línea y las macros, pero no creo que el orden en la expresión sea uno de ellos. Es decir, cualquiera de las funciones a, b, c o d podría reemplazarse con una macro, y podría ocurrir la misma secuenciación de los cuerpos de macro. La diferencia principal, me parece, es que con las funciones en línea, hay puntos de secuencia garantizados en las llamadas de función, como se describe en la respuesta principal, así como en los operadores de coma. Con las macros, se pierden los puntos de secuencia relacionados con la función. (Entonces, tal vez esa es una diferencia significativa ...) Sin embargo, en muchos aspectos, el problema es más bien como preguntas acerca de cuántos ángeles pueden bailar en la cabeza de un alfiler, no es muy importante en la práctica. Si alguien me presentara la expresión (a(),b()) + (c(),d()) en una revisión de código, le diría que reescriba el código para que quede claro:

a(); c(); x = b() + d();

Y eso supone que no hay requisitos de secuenciación críticos en b() vs d() .


Vea el Anexo C para una lista de puntos de secuencia. Las llamadas de función (el punto entre todos los argumentos que se evalúan y la ejecución que pasa a la función) son puntos de secuencia. Como ha dicho, no se especifica a qué función se llama primero, pero cada una de las dos funciones verá todos los efectos secundarios de la otra, o ninguna.