Considere la siguiente estructura:
struct s {
int a, b;
};
Típicamente 1 , esta estructura tendrá tamaño 8 y alineación 4.
¿Qué pasa si creamos dos struct s
objetos (más precisamente, escribimos en el almacenamiento asignado dos de estos objetos), con el segundo objeto superpuesto al primero?
char *storage = malloc(3 * sizeof(struct s));
struct s *o1 = (struct s *)storage; // offset 0
struct s *o2 = (struct s *)(storage + alignof(struct s)); // offset 4
// now, o2 points half way into o1
*o1 = (struct s){1, 2};
*o2 = (struct s){3, 4};
printf("o2.a=%d\n", o2->a);
printf("o2.b=%d\n", o2->b);
printf("o1.a=%d\n", o1->a);
printf("o1.b=%d\n", o1->b);
¿Hay algo sobre este programa de comportamiento indefinido? Si es así, ¿dónde queda indefinido? Si no es UB, ¿se garantiza que siempre imprima lo siguiente?
o2.a=3
o2.b=4
o1.a=1
o1.b=3
En particular, quiero saber qué sucede con el objeto señalado por o1
cuándo o2
, que se superpone, está escrito. ¿Todavía está permitido acceder a la parte no ocultada ( o1->a
)? ¿Acceder a la parte con capa es o1->b
simplemente lo mismo que acceder o2->a
?
¿Cómo se aplica el tipo efectivo aquí? Las reglas son lo suficientemente claras cuando habla de objetos no superpuestos y punteros que apuntan a la misma ubicación que la última tienda, pero cuando comienza a hablar sobre el tipo efectivo de porciones de objetos u objetos superpuestos, es menos claro.
¿Cambiaría algo si la segunda escritura fuera de un tipo diferente? Si los miembros fueran decir int
y short
no dos int
s?
Aquí hay un godbolt si quieres jugar con él allí.
1 Esta respuesta se aplica a plataformas donde este no es el caso también: por ejemplo, algunas pueden tener tamaño 4 y alineación 2. En una plataforma donde el tamaño y la alineación son las mismas, esta pregunta no se aplicaría ya que los objetos alineados y superpuestos ser imposible, pero no estoy seguro de si hay alguna plataforma como esa.
Respuestas:
Básicamente, esta es toda el área gris en el estándar; La estricta regla de alias especifica casos básicos y deja que el lector (y los proveedores del compilador) completen los detalles.
Se han realizado esfuerzos para escribir una regla mejor, pero hasta ahora no han dado lugar a ningún texto normativo y no estoy seguro de cuál es el estado de esto para C2x.
Como mencioné en mi respuesta a su pregunta anterior, la interpretación más común es que
p->q
significa(*p).q
y el tipo efectivo se aplica a todos*p
, a pesar de que luego procedemos a aplicar.q
.Según esta interpretación,
printf("o1.a=%d\n", o1->a);
causaría un comportamiento indefinido ya que el tipo efectivo de la ubicación*o1
no lo ess
(ya que parte de ella se ha sobrescrito).La justificación de esta interpretación se puede ver en una función como:
Con esta interpretación, la última línea podría optimizarse
puts("5");
, pero sin ella, el compilador tendría que considerar que la llamada a la función puede haber sidof(o1, o2);
y, por lo tanto, perder todos los beneficios que supuestamente proporciona la estricta regla de alias.Un argumento similar se aplica a dos tipos de estructura no relacionados que tienen un
int
miembro con un desplazamiento diferente.fuente
f(s* s1, s* s2)
, sinrestrict
, el compilador no puede asumirs1
ys2
son punteros diferentes. Creo que , una vez másrestrict
, ni siquiera puede suponer que no se superponen parcialmente. IAC, no veo que laf()
analogía demuestre bien la preocupación de OP . Buena suerte sin gruñir. UV para la primera mitad.s1 == s2
se permitiría, pero no una superposición parcial. (La optimización en mi ejemplo de código todavía podría realizarse sis1 == s2
)int
lugar de structs (y un sistema con_Alignof(int) < sizeof(int)
).p->q
y(*p).q
. Esto puede ser cierto para la interpretación de tipo como usted dice, pero no es cierto desde un punto de vista operativo. Es importante para los accesos concurrentes a la misma estructura que el acceso de un miembro no implique el acceso de ningún otro miembro.E1.E2
expresión no realiza el acceso (me refiero a laE1
expresión completa . Algunas de sus subexpresiones pueden realizar el acceso. Es decir, siE1
es así(*p)
, leer el valor del puntero al evaluar elp
acceso, pero la evaluación de*p
o(*p)
no realiza ninguna acceso). La regla de alias estricta no se aplica en caso de que no haya acceso.