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 intobjeto 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 xmiembro esté alineado correctamente. Con una matriz de dos struct fooobjetos, al menos uno u otro tendrá un xmiembro desalineado ).
(En este caso, p0apunta a una dirección desalineada, porque apunta a un intmiembro empaquetado que sigue a un charmiembro. p1Está alineado correctamente, ya que apunta al mismo miembro en el segundo elemento de la matriz, por lo que hay dos charobjetos que lo preceden - y en SPARC Solaris, la matriz arrparece estar asignada a una dirección que es par, pero no un múltiplo de 4.)
Al referirse al miembro xde a struct foopor nombre, el compilador sabe que xestá potencialmente desalineado y generará código adicional para acceder a él correctamente.
Una vez que la dirección de arr[0].xo arr[1].xse ha almacenado en un objeto puntero, ni el compilador ni el programa en ejecución saben que apunta a un intobjeto 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-memberopció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;
| ^~~~~~~~~