gcc的__attribute __((packed))/ #pragma pack不安全? [英] Is gcc's __attribute__((packed)) / #pragma pack unsafe?

查看:204
本文介绍了gcc的__attribute __((packed))/ #pragma pack不安全?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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



gcc提供了一个语言扩展, __ attribute __((packed)),它告诉编译器不要插入填充,允许struct成员将错位。例如,如果系统通常要求所有 int 对象具有4个字节的对齐方式, __ attribute __((packed))可以导致 int 结构成员以奇数偏移量分配。



引用gcc文档:

$ b'b


'packed'属性指定变量或结构字段
应该具有最小可能的对齐 - 一个字节用于变量
和一个字段,除非你用
`aligned'属性指定一个更大的值。


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



但是有没有这种情况是不安全的?编译器是否总是生成正确(虽然较慢)的代码来访问打包结构的未对齐成员?是否有可能在所有情况下都这样做?

解决方案

是, __属性__((打包))在某些系统上可能不安全。这个症状可能不会出现在x86上,这只会让问题更加阴险;在x86系统上进行测试不会发现问题。 (在x86上,未对齐的访问在硬件中处理;如果您取消引用指向奇数地址的 int * 指针,它将比正确地访问它慢一点)



在其他一些系统上,如SPARC,试图访问未对齐的 int object导致总线错误,导致程序崩溃。



还有一些系统,一个错位的访问静静地忽略了地址的低位,导致它访问错误的内存块。



请考虑以下程序:

  #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 \\\
,(int)sizeof(struct foo));
printf(offsetof(struct foo,c)=%d \\\
,(int)offsetof(struct foo,c));
printf(offsetof(struct foo,x)=%d \\\
,(int)offsetof(struct foo,x));
printf(arr [0] .x =%d \\\
,arr [0] .x);
printf(arr [1] .x =%d \\\
,arr [1] .x);
printf(p0 =%p \ n,(void *)p0);
printf(p1 =%p\\\
,(void *)p1);
printf(* p0 =%d \\\
,* p0);
printf(* p1 =%d \\\
,* p1);
返回0;
}

在具有gcc 4.5.2的x86 Ubuntu上,它会生成以下输出: / p>

  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
总线错误

在这两种情况下,程序都没有额外的选项编译,只是 gcc packed.c -o packed



(使用单个结构而非数组的程序不能可靠地显示这个问题,因为编译器可以分配一个奇数地址的结构,所以 x 成员正确对齐编辑。对于两个 struct foo 对象的数组,至少有一个或另一个会有一个未对齐的 x 成员。)



(在这种情况下, p0 指向一个未对齐的地址,因为它指向一个打包的 int 成员跟在 char 成员之后。 p1 恰好对齐,因为它指向数组的第二个元素中的同一个成员,所以在它之前有两个 char 对象 - 在SPARC Solaris上,数组 arr 似乎分配在偶数地址,但不是4的倍数。)



当引用成员 x 可能未对齐,并会生成额外的代码来正确访问它。



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



我认为在gcc中修复这个问题是不切实际的。 (a)在编译时证明指针不指向打包结构的未对齐成员,或者(b)在指定任何类型的指针时,生成体积较大,速度较慢的代码,可以处理对齐或未对齐的对象。



我提交了 gcc错误报告。正如我所说,我不认为修复它是可行的,但文件应该提及它(目前不)。

In C, the compiler will lay out members of a struct in the order in which they're declared, with possible padding bytes inserted between members, or after the last member, to ensure that each member is aligned properly.

gcc provides a language extension, __attribute__((packed)), which tells the compiler not to insert padding, allowing struct members to be misaligned. For example, if the system normally requires all int objects to have 4-byte alignment, __attribute__((packed)) can cause int struct members to be allocated at odd offsets.

Quoting the gcc documentation:

The `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 `aligned' attribute.

Obviously the use of this extension can result in smaller data requirements but slower code, as the compiler must (on some platforms) generate code to access a misaligned member a byte at a time.

But are there any cases where this is unsafe? Does the compiler always generate correct (though slower) code to access misaligned members of packed structs? Is it even possible for it to do so in all cases?

解决方案

Yes, __attribute__((packed)) is potentially unsafe on some systems. The symptom probably won't show up on an x86, which just makes the problem more insidious; testing on x86 systems won't reveal the problem. (On the x86, misaligned accesses are handled in hardware; if you dereference an int* pointer that points to an odd address, it will be a little slower than if it were properly aligned, but you'll get the correct result.)

On some other systems, such as SPARC, attempting to access a misaligned int object causes a bus error, crashing the program.

There have also been systems where a misaligned access quietly ignores the low-order bits of the address, causing it to access the wrong chunk of memory.

Consider the following program:

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

On x86 Ubuntu with gcc 4.5.2, it produces the following output:

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

On SPARC Solaris 9 with gcc 4.5.1, it produces the following:

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

In both cases, the program is compiled with no extra options, just gcc packed.c -o packed.

(A program that uses a single struct rather than array doesn't reliably exhibit the problem, since the compiler can allocate the struct on an odd address so the x member is properly aligned. With an array of two struct foo objects, at least one or the other will have a misaligned x member.)

(In this case, p0 points to a misaligned address, because it points to a packed int member following a char member. p1 happens to be correctly aligned, since it points to the same member in the second element of the array, so there are two char objects preceding it -- and on SPARC Solaris the array arr appears to be allocated at an address that is even, but not a multiple of 4.)

When referring to the member x of a struct foo by name, the compiler knows that x is potentially misaligned, and will generate additional code to access it correctly.

Once the address of arr[0].x or arr[1].x has been stored in a pointer object, neither the compiler nor the running program knows that it points to a misaligned int object. It just assumes that it's properly aligned, resulting (on some systems) in a bus error or similar other failure.

Fixing this in gcc would, I believe, be impractical. A general solution would require, for each attempt to dereference a pointer to any type with non-trivial alignment requirements either (a) proving at compile time that the pointer doesn't point to a misaligned member of a packed struct, or (b) generating bulkier and slower code that can handle either aligned or misaligned objects.

I've submitted a gcc bug report. As I said, I don't believe it's practical to fix it, but the documentation should mention it (it currently doesn't).

这篇关于gcc的__attribute __((packed))/ #pragma pack不安全?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆