Vidas literales compuestas y si bloques

Esta es una pregunta teórica, sé cómo hacerlo sin ambigüedades, pero sentí curiosidad y me metí en el estándar y necesito un segundo par de ojos de abogado de estándares.

Empecemos con dos estructuras y una función init:

struct foo { int a; }; struct bar { struct foo *f; }; struct bar * init_bar(struct foo *f) { struct bar *b = malloc(sizeof *b); if (!b) return NULL; b->f = f; return b; } 

Ahora tenemos un progtwigdor descuidado que no verifica los valores de retorno:

 void x(void) { struct bar *b; b = init_bar(&((struct foo){ .a = 42 })); b->f->a++; free(b); } 

Desde mi lectura del estándar, no hay nada de malo aquí que no sea la posibilidad de anular la referencia a un puntero NULO. La modificación de la struct foo través del puntero en la struct bar debe ser legal porque la vida útil del literal compuesto enviado a init_bar es el bloque donde está contenido, que es la función completa x .

Pero ahora tenemos un progtwigdor más cuidadoso:

 void y(void) { struct bar *b; if ((b = init_bar(&((struct foo){ .a = 42 }))) == NULL) err(1, "couldn't allocate b"); b->f->a++; free(b); } 

El código hace lo mismo, ¿verdad? Así que debería funcionar también. Pero una lectura más cuidadosa del estándar C11 me está llevando a creer que esto conduce a un comportamiento indefinido. (énfasis en las citas mías)

6.5.2.5 Literales compuestos

5 El valor del literal compuesto es el de un objeto sin nombre inicializado por la lista de inicializadores. Si el literal compuesto ocurre fuera del cuerpo de una función, el objeto tiene una duración de almacenamiento estático; de lo contrario, tiene una duración de almacenamiento automático asociada con el bloque adjunto .

6.8.4 Declaraciones de selección

3 Una statement de selección es un bloque cuyo scope es un subconjunto estricto del scope de su bloque adjunto. Cada subestación asociada es también un bloque cuyo scope es un subconjunto estricto del scope de la statement de selección.

¿Estoy leyendo esto bien? ¿El hecho de que el if es un bloque significa que la vida útil del literal compuesto es solo la instrucción if?

(En caso de que alguien se pregunte de dónde provino este ejemplo ideado, en el código real, init_bar es en realidad pthread_create y el hilo se une antes de que regrese la función, pero no quería enturbiar las aguas involucrando hilos).

La segunda parte del Estándar que citó (6.8.4 declaraciones de selección) dice esto. En codigo:

 {//scope 1 if( ... )//scope 2 { }//end scope 2 }//end scope 1 

El scope 2 está totalmente dentro del scope 1. Tenga en cuenta que una statement de selección en este caso es la instrucción if completa, no solo los paréntesis:

 if( ... ){ ... } 

Cualquier cosa definida en esa statement está en el scope 2. Por lo tanto, como se muestra en su tercer ejemplo, la vida útil del literal compuesto, que se declara en el scope 2, termina en el corchete de cierre ( scope final 2 ), por lo que ese ejemplo causará comportamiento indefinido si la función devuelve non-NULL (o NULL si err () no termina el progtwig).

(Tenga en cuenta que utilicé corchetes en la statement if, aunque el tercer ejemplo no los usa. Esa parte del ejemplo es equivalente a esta ( 6.8.2 statement compuesta ):

 if ((b = init_bar(&((struct foo){ .a = 42 }))) == NULL) { err(1, "couldn't allocate b"); }