Sim, __attribute__((packed))
é potencialmente inseguro em alguns sistemas. O sintoma provavelmente não aparecerá em um x86, o que apenas torna o problema mais insidioso; testes em sistemas x86 não revelam o problema. (No x86, os acessos desalinhados são tratados no hardware; se você desferir um int*
ponteiro que aponte para um endereço ímpar, será um pouco mais lento do que se estivesse alinhado corretamente, mas você obterá o resultado correto.)
Em alguns outros sistemas, como o SPARC, a tentativa de acessar um int
objeto desalinhado causa um erro de barramento, travando o programa.
Também existem sistemas nos quais um acesso desalinhado ignora silenciosamente os bits de ordem inferior do endereço, fazendo com que ele acesse o bloco de memória errado.
Considere o seguinte 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;
}
No x86 Ubuntu com gcc 4.5.2, ele produz a seguinte saída:
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
No SPARC Solaris 9 com gcc 4.5.1, ele produz o seguinte:
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
Nos dois casos, o programa é compilado sem opções extras, apenas gcc packed.c -o packed
.
(Um programa que usa uma única estrutura em vez de uma matriz não exibe o problema de maneira confiável, pois o compilador pode alocar a estrutura em um endereço ímpar para que o x
membro esteja alinhado corretamente. Com uma matriz de dois struct foo
objetos, pelo menos um ou outro terá um x
membro desalinhado .)
(Nesse caso, p0
aponta para um endereço desalinhado, porque aponta para um int
membro compactado após um char
membro. p1
Está alinhado corretamente, pois aponta para o mesmo membro no segundo elemento da matriz, portanto, existem dois char
objetos antes dele - e no SPARC Solaris, o array arr
parece estar alocado em um endereço uniforme, mas não múltiplo de 4.)
Ao se referir ao membro x
de um struct foo
nome, o compilador sabe que x
está potencialmente desalinhado e gerará código adicional para acessá-lo corretamente.
Depois que o endereço arr[0].x
ou arr[1].x
foi armazenado em um objeto ponteiro, nem o compilador nem o programa em execução sabem que apontam para um int
objeto desalinhado . Apenas assume que está alinhado corretamente, resultando (em alguns sistemas) em um erro de barramento ou outra falha semelhante.
Consertar isso no gcc seria, na minha opinião, impraticável. Uma solução geral exigiria, para cada tentativa de desreferenciar um ponteiro para qualquer tipo com requisitos de alinhamento não triviais (a) provar em tempo de compilação que o ponteiro não aponta para um membro desalinhado de uma estrutura empacotada, ou (b) gerar código mais lento e volumoso que pode manipular objetos alinhados ou desalinhados.
Enviei um relatório de bug do gcc . Como eu disse, não acredito que seja prático corrigi-lo, mas a documentação deve mencioná-lo (atualmente não o faz).
ATUALIZAÇÃO : A partir de 20/12/2018, esse bug está marcado como CORRIGIDO. O patch aparecerá no gcc 9 com a adição de uma nova -Waddress-of-packed-member
opção, ativada por padrão.
Quando o endereço do membro compactado da estrutura ou união é obtido, isso pode resultar em um valor de ponteiro não alinhado. Este patch adiciona -Waddress-of -pack-member para verificar o alinhamento na atribuição do ponteiro e avisar o endereço não alinhado, bem como o ponteiro não alinhado
Acabei de criar essa versão do gcc a partir do código-fonte. Para o programa acima, ele produz estes 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;
| ^~~~~~~~~