En C99, ¿es f () + g () indefinido o simplemente no especificado?

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 sería no especificado: o f () se llamaría antes de g (), o g () antes de f ().

Ya no estoy tan seguro. ¿Qué sucede si el comstackdor especifica las funciones (que el comstackdor puede decidir incluso si las funciones no están declaradas en inline ) y luego reordena las instrucciones? ¿Puede uno obtener un resultado diferente de los dos anteriores? En otras palabras, ¿es este comportamiento indefinido?

Esto no es porque tengo la intención de escribir este tipo de cosas, esto es para elegir la mejor etiqueta para dicha statement en un analizador estático.

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 evalúen todos los cero argumentos); uno antes de la llamada a g() (después de que se evalúen todos los cero argumentos); uno como la llamada a f() regresa; y uno cuando regresa la llamada a g() . Además, los dos puntos de secuencia asociados con f() ocurren tanto antes como después de los dos puntos de secuencia asociados con g() . Lo que no puede decir es en qué orden ocurrirán los puntos de secuencia, si los puntos f ocurren antes de los puntos g o viceversa.

Incluso si el comstackdor ingresó 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 scope del daño (suponiendo un comstackdor sin errores).

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


En un comentario, supercat pregunta:

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

Creo que la regla ‘como si’ se aplica y el comstackdor no obtiene latitud adicional para omitir puntos de secuencia porque usa una función explícitamente en inline . La razón principal para pensar que (siendo demasiado perezoso para buscar la redacción exacta en el estándar) es que al comstackdor se le permite alinear o no una función en línea de acuerdo con sus reglas, pero el comportamiento del progtwig no debe cambiar (a excepción de actuación).

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

  • Claramente, a se ejecuta antes que b y c se ejecuta antes d. Creo que es posible que cyd se ejecute entre ayb, aunque es bastante improbable que el comstackdor genere el código de esa manera; de forma similar, a y b podrían ejecutarse entre c y d. Y aunque usé ‘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 un pedido de ≺ b, c ≺ d)
    • acbd
    • acdb
    • cadb
    • cabd

    Creo que cubre todas las secuencias posibles. Ver también el chat entre Jonathan Leffler y AnArrayOfFunctions : la esencia es que AnArrayOfFunctions no cree que las secuencias ‘posiblemente permitidas’ estén permitidas en absoluto.

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 por una macro, y podría ocurrir la misma secuencia de macrocuerpos. La principal diferencia, me parece, es que con las funciones en línea, hay puntos de secuencia garantizados en las llamadas a funciones, como se indica en la respuesta principal, así como en los operadores de coma. Con las macros, pierde los puntos de secuencia relacionados con la función. (Entonces, tal vez esa es una diferencia significativa …) Sin embargo, en muchos sentidos, el problema es más bien como preguntas sobre cuántos ángeles pueden bailar en la punta 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 del código, les diría que reescriban el código para dejarlo en claro:

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

Y eso supone que no existe un requisito de secuencia crítica en b() frente a d() .

Ver Anexo C para una lista de puntos de secuencia. Las llamadas a funciones (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 qué función se llama primero, pero cada una de las dos funciones verá todos los efectos secundarios de la otra o ninguna.

@dmckee

Bueno, eso no encajará dentro de un comentario, pero aquí está la cosa:

Primero, escribe un analizador estático correcto. “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 que no debe advertir dos veces sobre un mismo problema … Sea una alarma verdadera o falsa (no se sabe cuál, nunca se sabe cuál, de lo contrario sería demasiado fácil).

Entonces, quieres emitir una sola advertencia para

 *p = x; y = *p; 

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

Entonces le enseña a su analizador que asum 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 ligeros para el rest de ellos, según los resultados del análisis inicial correcto. Digamos, un cortador de progtwigs en C.

Y les dice “ellos”: no tiene que verificar todas las alarmas (posiblemente falsas) emitidas por el primer análisis. El progtwig rebanado se comporta igual que el progtwig original, siempre que ninguno de ellos se active. El slicer produce progtwigs 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 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 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 se deben excluir 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 progtwigdores escribirán lo primero sin siquiera pensarlo, y cuando diga que su rebanadora excluye los “comportamientos incorrectos” del progtwig, 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.