¿Por qué se permite a gcc cargar especulativamente desde una estructura?

Ejemplo que muestra la optimización de gcc y el código de usuario que pueden fallar

La función 'foo' en el fragmento a continuación cargará solo uno de los miembros de estructura A o B; bueno, al menos esa es la intención del código no optimizado.

typedef struct {
  int A;
  int B;
} Pair;

int foo(const Pair *P, int c) {
  int x;
  if (c)
    x = P->A;
  else
    x = P->B;
  return c/102 + x;
}

Esto es lo que da gcc -O3:

mov eax, esi
mov edx, -1600085855
test esi, esi
mov ecx, DWORD PTR [rdi+4]   <-- ***load P->B**
cmovne ecx, DWORD PTR [rdi]  <-- ***load P->A***
imul edx
lea eax, [rdx+rsi]
sar esi, 31
sar eax, 6
sub eax, esi
add eax, ecx
ret

Entonces parece que a gcc se le permite cargar especulativamente ambos miembros de la estructura para eliminar la ramificación. Pero entonces, ¿se considera el siguiente código comportamiento indefinido o es ilegal la optimización de gcc anterior?

#include <stdlib.h>  

int naughty_caller(int c) {
  Pair *P = (Pair*)malloc(sizeof(Pair)-1); // *** Allocation is enough for A but not for B ***
  if (!P) return -1;

  P->A = 0x42; // *** Initializing allocation only where it is guaranteed to be allocated ***

  int res = foo(P, 1); // *** Passing c=1 to foo should ensure only P->A is accessed? ***

  free(P);
  return res;
}

Si la especulación de carga ocurrirá en el escenario anterior, existe la posibilidad de que cargar P-> B cause una excepción porque el último byte de P-> B puede estar en la memoria no asignada. Esta excepción no sucederá si la optimización está desactivada.

La pregunta

¿Es legal la optimización de gcc que se muestra arriba de la especulación de carga? ¿Dónde dice o implica la especificación que está bien? Si la optimización es legal, ¿cómo se convierte el código en 'naughtly_caller' en un comportamiento indefinido?

Respuestas a la pregunta(6)

Su respuesta a la pregunta