Sí, __attribute__((packed))
es potencialmente inseguro en algunos sistemas. El síntoma probablemente no aparecerá en un x86, lo que hace que el problema sea más insidioso; Las pruebas en sistemas x86 no revelarán el problema. (En el x86, los accesos desalineados se manejan en el hardware; si desreferencia un int*
puntero que apunta a una dirección impar, será un poco más lento que si estuviera correctamente alineado, pero obtendrá el resultado correcto).
En algunos otros sistemas, como SPARC, intentar acceder a un int
objeto desalineado provoca un error de bus que bloquea el programa.
También ha habido sistemas en los que un acceso desalineado ignora silenciosamente los bits de orden inferior de la dirección, lo que hace que acceda al fragmento de memoria incorrecto.
Considere el siguiente programa:
#include <stdio.h>
#include <stddef.h>
int main(void)
{
struct foo {
char c;
int x;
} __attribute__((packed));
struct foo arr[2] = { { 'a', 10 }, {'b', 20 } };
int *p0 = &arr[0].x;
int *p1 = &arr[1].x;
printf("sizeof(struct foo) = %d\n", (int)sizeof(struct foo));
printf("offsetof(struct foo, c) = %d\n", (int)offsetof(struct foo, c));
printf("offsetof(struct foo, x) = %d\n", (int)offsetof(struct foo, x));
printf("arr[0].x = %d\n", arr[0].x);
printf("arr[1].x = %d\n", arr[1].x);
printf("p0 = %p\n", (void*)p0);
printf("p1 = %p\n", (void*)p1);
printf("*p0 = %d\n", *p0);
printf("*p1 = %d\n", *p1);
return 0;
}
En Ubuntu x86 con gcc 4.5.2, produce el siguiente resultado:
sizeof(struct foo) = 5
offsetof(struct foo, c) = 0
offsetof(struct foo, x) = 1
arr[0].x = 10
arr[1].x = 20
p0 = 0xbffc104f
p1 = 0xbffc1054
*p0 = 10
*p1 = 20
En SPARC Solaris 9 con gcc 4.5.1, produce lo siguiente:
sizeof(struct foo) = 5
offsetof(struct foo, c) = 0
offsetof(struct foo, x) = 1
arr[0].x = 10
arr[1].x = 20
p0 = ffbff317
p1 = ffbff31c
Bus error
En ambos casos, el programa se compila sin opciones adicionales, solo gcc packed.c -o packed
.
(Un programa que utiliza una sola estructura en lugar de una matriz no presenta el problema de manera confiable, ya que el compilador puede asignar la estructura en una dirección impar para que el x
miembro esté alineado correctamente. Con una matriz de dos struct foo
objetos, al menos uno u otro tendrá un x
miembro desalineado ).
(En este caso, p0
apunta a una dirección desalineada, porque apunta a un int
miembro empaquetado que sigue a un char
miembro. p1
Está alineado correctamente, ya que apunta al mismo miembro en el segundo elemento de la matriz, por lo que hay dos char
objetos que lo preceden - y en SPARC Solaris, la matriz arr
parece estar asignada a una dirección que es par, pero no un múltiplo de 4.)
Al referirse al miembro x
de a struct foo
por nombre, el compilador sabe que x
está potencialmente desalineado y generará código adicional para acceder a él correctamente.
Una vez que la dirección de arr[0].x
o arr[1].x
se ha almacenado en un objeto puntero, ni el compilador ni el programa en ejecución saben que apunta a un int
objeto desalineado . Simplemente se supone que está alineado correctamente, lo que resulta (en algunos sistemas) en un error del bus u otra falla similar.
Arreglar esto en gcc sería, en mi opinión, poco práctico. Una solución general requeriría, para cada intento de desreferenciar un puntero a cualquier tipo con requisitos de alineación no triviales, ya sea (a) probar en tiempo de compilación que el puntero no apunta a un miembro desalineado de una estructura empaquetada, o (b) generar código más voluminoso y lento que puede manejar objetos alineados o desalineados.
He enviado un informe de error de gcc . Como dije, no creo que sea práctico arreglarlo, pero la documentación debería mencionarlo (actualmente no lo hace).
ACTUALIZACIÓN : A partir del 2018-12-20, este error está marcado como FIJO. El parche aparecerá en gcc 9 con la adición de una nueva -Waddress-of-packed-member
opción, habilitada por defecto.
Cuando se toma la dirección del miembro empaquetado de struct o union, puede resultar en un valor de puntero no alineado. Este parche agrega -Waddress-of-pack-member para verificar la alineación en la asignación del puntero y advertir la dirección no alineada, así como el puntero no alineado
Acabo de construir esa versión de gcc desde la fuente. Para el programa anterior, produce estos diagnósticos:
c.c: In function ‘main’:
c.c:10:15: warning: taking address of packed member of ‘struct foo’ may result in an unaligned pointer value [-Waddress-of-packed-member]
10 | int *p0 = &arr[0].x;
| ^~~~~~~~~
c.c:11:15: warning: taking address of packed member of ‘struct foo’ may result in an unaligned pointer value [-Waddress-of-packed-member]
11 | int *p1 = &arr[1].x;
| ^~~~~~~~~