C "comportamiento observable" en el contexto de UB "comportamiento indefinido"

(La pregunta fue originada originalmente por los comentarios de esta respuesta a¿Existen condiciones de carrera en esta implementación productor-consumidor? pero se pregunta aquí estrictamente desde la perspectiva del lenguaje C, sin ninguna concurrencia o subprocesamiento múltiple involucrado).

Considere este código mínimo:

#define BUFSIZ 10
char buf[BUFSIZ];

void f(int *pn)
{
    buf[*pn]++;
    *pn = (*pn + 1) % BUFSIZ;
}

int main()
{
    int n = 0;
    f(&n);
    return n; 
}

Pregunta: ¿la C"como si" Qué reglas permiten al compilador reescribir el código de la siguiente manera?

void f(int *pn)
{
    int n = *pn;
    *pn = (*pn + 1) % BUFSIZ;
    buf[n]++;
}

Por un lado, lo anterior no cambiaría el comportamiento observable del programa tal como está escrito.

Por otra parte,f podría llamarse con un índice no válido, posiblemente desde otra unidad de traducción:

int g()
{
    int n = -1001;
    f(&n);
}

En este último caso, ambas variantes del código invocarían a UB al acceder al elemento de matriz fuera de los límites. Sin embargo, el código original dejaría*pn en el valor que se pasa af (= -1001) mientras que el código reescrito entraría en UB-land solo después de modificar*pn (a0)

¿Tal diferencia contaría como "observable" o, volviendo a la pregunta real, hay algo en el estándar C que permita o impida específicamente este tipo de reescritura / optimización de código?

Respuestas a la pregunta(1)

Su respuesta a la pregunta