Escriba punning con void*sin romper la regla de alias estricta en C99
void-pointers strict-aliasing (1)
Hace poco me encontré con la estricta regla de alias, pero tengo problemas para entender cómo usar void *
para realizar el punteo de tipo sin infringir la regla.
Sé que esto rompe la regla:
int x = 0xDEADBEEF;
short *y = (short *)&x;
*y = 42;
int z = x;
Y sé que puedo usar de forma segura una unión en C99 para la tipificación de tipos:
union{
int x;
short y;
} data;
data.x = 0xDEADBEEF;
data.y = 42;
int z = data.x;
Pero, ¿cómo uso void *
para realizar el punteo de tipo de forma segura en C99? Es correcto lo siguiente:
int x = 0xDEADBEEF;
void * helper = (void *)&x;
short *y = (short *)helper;
*y = 42;
int z = x;
Sospecho que el código aún romperá la regla de alias estricta ya que la memoria en la dirección de la variable x
se puede modificar tanto con x
como con una y
referencia.
Si el punteo de tipo no está definido a través de void *
, ¿cuál es el propósito del void *
en C99?
void *
no tiene nada que ver con el tipeado. Sus principales objetivos son:
Para permitir la asignación genérica y las operaciones de liberación que no se preocupan por el tipo de objeto que la persona que llama está almacenando allí (por ejemplo,
malloc
yfree
).Para permitir que una persona que llama pase un puntero a un tipo arbitrario a través de una función que lo devolverá a través de una devolución de llamada (por ejemplo,
qsort
ypthread_create
). En este caso, el compilador no puede imponer la verificación de tipos; es su responsabilidad al escribir la persona que llama y la devolución de llamada para asegurarse de que la devolución de llamada acceda al objeto con el tipo correcto.
Los punteros para void
también se usan en algunos lugares (como memcpy
) que realmente operan en un objeto como la representación de caracteres unsigned char []
superpuesta para el objeto. Esto podría verse como una tipificación de tipo, pero no es una violación de alias porque los tipos char
pueden alias cualquier cosa para acceder a su representación. En este caso, el unsigned char *
también funcionaría, pero void *
tiene la ventaja de que los punteros se convierten automáticamente en void *
.
En su ejemplo, dado que el tipo original es int
y no una unión, no existe una forma legal de escribir con letra y acceder a él de manera short
. En su lugar, podría copiar el valor de x
en una unión, realizar una tipificación de tipos bien definida allí, y luego copiarlo de nuevo. Un buen compilador debe omitir la copia por completo. Alternativamente, podría dividir la escritura en escrituras de caracteres y luego sería un alias legal.