是的,__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
or的地址arr[1].x
被存储在一个指针对象中,编译器和运行程序都不知道它指向了一个未对齐的int
对象。它只是假设它已正确对齐,从而(在某些系统上)导致总线错误或类似的其他故障。
我相信在 gcc 中解决这个问题是不切实际的。一个通用的解决方案将要求,对于每次尝试取消引用指向具有非平凡对齐要求的任何类型的指针,要么(a)在编译时证明指针不指向压缩结构的未对齐成员,要么(b)生成可以处理对齐或未对齐对象的更大更慢的代码。
我已经提交了gcc 错误报告。正如我所说,我认为修复它不切实际,但文档应该提到它(目前没有)。
更新:截至 2018 年 12 月 20 日,此错误被标记为已修复。该补丁将出现在 gcc 9 中,并添加了一个-Waddress-of-packed-member
默认启用的新选项。
当获取struct或union的packed成员的地址时,可能会导致指针值未对齐。此补丁添加了 -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;
| ^~~~~~~~~