是的,__attribute__((packed))
在某些系统上可能不安全。该症状可能不会出现在x86上,这只会使问题更加隐蔽。在x86系统上进行测试不会发现问题。(在x86上,未对齐的访问是在硬件中处理的;如果取消引用int*
指向奇数地址的指针,则它会比正确对齐的指针慢一点,但会得到正确的结果。)
在某些其他系统(例如SPARC)上,尝试访问未对齐的int
对象会导致总线错误,从而使程序崩溃。
在某些系统中,未对齐的访问会悄悄地忽略地址的低位,从而导致访问错误的内存块。
考虑以下程序:
#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;
}
在具有gcc 4.5.2的x86 Ubuntu上,它将产生以下输出:
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
在带有gcc 4.5.1的SPARC Solaris 9上,它将产生以下内容:
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
在这两种情况下,程序仅编译时就没有其他选项gcc packed.c -o packed
。
(使用单个结构而不是数组的程序无法可靠地显示该问题,因为编译器可以将结构分配给奇数地址,从而使该x
成员正确对齐。对于两个struct foo
对象组成的数组,至少一个或另一个将具有未对齐的x
成员。)
(在这种情况下,p0
指向未对齐的地址,因为它指向int
紧随一个char
成员的压缩成员。p1
碰巧正确对齐,因为它指向数组第二个元素中的同一成员,因此在char
它之前有两个对象-并且在SPARC Solaris上,阵列arr
分配的地址似乎是偶数,但不是4的倍数。)
当按名称引用x
a 的成员时struct foo
,编译器会知道该x
名称可能未对齐,并将生成其他代码以正确访问它。
一旦的地址arr[0].x
或arr[1].x
已经存储在指针对象中,编译器和运行的程序都不会知道它指向未对齐的int
对象。它只是假定它已正确对齐,从而(在某些系统上)导致总线错误或类似的其他故障。
我认为,在gcc中修复此问题不切实际。对于每次尝试将指针解除对具有非平凡对齐要求的类型的尝试,通用解决方案都需要(a)在编译时证明指针没有指向打包结构的未对齐成员,或者(b)生成可以处理对齐或未对齐对象的笨重且较慢的代码。
我已经提交了gcc错误报告。正如我所说,我认为修复它不切实际,但是文档中应该提到它(目前还没有)。
更新:从2018年12月20日起,此错误被标记为已修复。该补丁将在gcc 9中显示,并添加一个新-Waddress-of-packed-member
选项,默认情况下启用。
当采用struct或union的打包成员的地址时,可能会导致指针值未对齐。此补丁添加了-Waddress-of-packed-member来检查指针分配时的对齐方式,并警告未对齐的地址以及未对齐的指针
我刚刚从源代码构建了该版本的gcc。对于上述程序,它会产生以下诊断信息:
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;
| ^~~~~~~~~