是的,__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 Solaris9上,它产生以下结果:
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
个.
(使用单个 struct 而不是数组的程序不会可靠地出现该问题,因为编译器可以在奇数地址上分配 struct ,从而使x
成员正确对齐.对于包含两个struct foo
个对象的数组,至少有一个或另一个将具有未对齐的x
成员.)
(在本例中,p0
指向未对齐的地址,因为它指向char
成员之后的压缩int
成员.p1
恰好正确对齐,因为它指向数组第二个元素中的同一个成员,所以它前面有两个char
对象——在SPARC Solaris上,数组arr
似乎分配在一个偶数,但不是4的倍数.)
当按名称引用struct foo
的成员x
时,编译器知道x
可能未对齐,并将生成额外的代码来正确访问它.
一旦arr[0].x
或arr[1].x
的地址存储在指针对象中,编译器和运行的程序都不知道它指向未对齐的int
对象.它只是假设它正确对齐,导致(在某些系统上)总线错误或类似的其他故障.
我认为,在gcc中解决这个问题是不切实际的.一般的解决方案要求,每次try 取消对任何类型的指针的引用时,都需要(A)在编译时证明指针没有指向压缩 struct 的未对齐成员,或者(b)生成更大、更慢的代码,可以处理对齐或未对齐的对象.
我已经提交了一份gcc bug report美元的申请.正如我所说的,我不认为修复它是切实可行的,但文档应该提到它(目前没有).
UPDATE:从2018年12月20日起,此错误被标记为已修复.该补丁将出现在gcc 9中,并添加一个默认启用的新-Waddress-of-packed-member
选项.
当获取 struct 或联合的打包成员的地址时,它可能
我刚刚从源头上构建了那个版本的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;
| ^~~~~~~~~