gcc的__属性_((打包))/#务实包不安全吗?

内容来源于 Stack Overflow,并遵循CC BY-SA 3.0许可协议进行翻译与使用

  • 回答 (2)
  • 关注 (0)
  • 查看 (15)

在C中,编译器将按照声明的顺序排列结构的成员,在成员之间插入可能的填充字节,或者在最后一个成员之后插入字节,以确保每个成员正确地对齐。

GCC提供了语言扩展,__attribute__((packed)),它告诉编译器不要插入填充,从而允许结构成员对齐。例如,如果系统通常需要所有int对象具有4字节对齐,__attribute__((packed))能引起int在奇数偏移量处分配结构成员。

引用GCC文档:

packed' attribute specifies that a variable or structure field should have the smallest possible alignment--one byte for a variable, and one bit for a field, unless you specify a larger value with the对齐属性

显然,使用这个扩展会导致较小的数据需求,但代码会更慢,因为编译器必须(在某些平台上)生成代码,以便一次访问错误对齐的成员。

但有什么不安全的情况吗?编译器是否总是生成正确的(尽管速度较慢)代码来访问打包结构的错误对齐成员?在所有情况下,它甚至有可能这样做吗?

提问于
用户回答回答于

是的,__attribute__((packed))在某些系统上可能不安全。这种症状可能不会出现在x86上,这只会使问题更加危险;在x86系统上进行测试不会发现问题。

考虑以下方案:

#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;
}

在X86 Ubuntu上,它用GCC 4.5.2生成以下输出:

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)

(In this case, p0指向错误对齐地址,因为它指向打包的地址。int成员跟随char会员。p1因为它指向数组的第二个元素中的同一个成员,所以有两个char对象之前的对象--并在SPARC Solaris数组上。arr似乎是在一个偶数的地址上分配的,但不是4的倍数。)

当提到成员时x一种struct foo根据名称,编译器知道x可能出现对齐错误,并将生成其他代码以正确访问它。

一旦地址arr[0].xarr[1].x已存储在指针对象中,编译器和正在运行的程序都不知道它指向对齐错误。int对象。它只是假设它正确地对齐,导致(在某些系统上)出现总线错误或类似的其他故障。

用户回答回答于

只要您始终通过结构通过.(点)或->符号。

什么是不安全的是采取未对齐的数据的指针,然后访问它没有考虑到这一点。 另外,尽管结构中的每个项目都是未知的,但已知它们是以特定的方式未对齐的,所以整个结构必须与编译器期望的一致,否则就会出现问题(在某些平台上,或者 在将来如果发明一种新方法来优化未对齐的访问)。

扫码关注云+社区