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

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

问题描述

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

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 提供了一个语言扩展,__attribute__((packed)),它告诉编译器不要插入填充,允许结构成员未对齐.例如,如果系统通常要求所有 int 对象具有 4 字节对齐,则 __attribute__((packed)) 会导致 int 结构体成员以奇数偏移量分配.

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.

引用 gcc 文档:

`packed' 属性指定变量或结构字段应该有最小的对齐方式——一个字节的变量,和一个字段的一位,除非您指定一个更大的值`aligned' 属性.

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?

推荐答案

是的,__attribute__((packed)) 在某些系统上可能不安全.该症状可能不会出现在 x86 上,这只会使问题更加隐蔽;在 x86 系统上进行测试不会发现问题.(在 x86 上,未对齐的访问在硬件中处理;如果您取消引用指向奇地址的 int* 指针,它会比正确对齐时慢一点,但是您将得到正确的结果.)

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.)

在某些其他系统(例如 SPARC)上,尝试访问未对齐的 int 对象会导致总线错误,从而导致程序崩溃.

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.

考虑以下程序:

#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
", (void*)p0);
    printf("p1 = %p
", (void*)p1);
    printf("*p0 = %d
", *p0);
    printf("*p1 = %d
", *p1);
    return 0;
}

在带有 gcc 4.5.2 的 x86 Ubuntu 上,它产生以下输出:

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

在带有 gcc 4.5.1 的 SPARC Solaris 9 上,它生成以下内容:

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

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

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

(使用单个结构而不是数组的程序不能可靠地显示该问题,因为编译器可以在奇数地址上分配结构,因此 x 成员正确对齐.使用由两个 struct foo 对象组成的数组,其中至少一个将具有未对齐的 x 成员.)

(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.)

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

(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.)

当按名称引用 struct foo 的成员 x 时,编译器知道 x 可能未对齐,并将生成额外的代码以正确访问它.

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.

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

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.

我相信在 gcc 中修复这个问题是不切实际的.对于每次尝试取消对具有非平凡对齐要求的任何类型的指针的引用,通用解决方案都需要 (a) 在编译时证明该指针未指向打包结构的未对齐成员,或 (b)生成可以处理对齐或未对齐对象的体积更大、速度更慢的代码.

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.

我已经提交了 gcc 错误报告.正如我所说,我认为修复它不切实际,但文档应该提到它(目前没有).

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).

更新:截至 2018 年 12 月 20 日,此错误已标记为已修复.该补丁将出现在 gcc 9 中,并添加了一个默认启用的新 -Waddress-of-packed-member 选项.

UPDATE: As of 2018-12-20, this bug is marked as FIXED. The patch will appear in gcc 9 with the addition of a new -Waddress-of-packed-member option, enabled by default.

struct 或 union 的打包成员的地址被取时,可能导致未对齐的指针值.这个补丁增加了-Waddress-of-packed-member 在指针分配时检查对齐并警告未对齐的地址以及未对齐的指针

When address of packed member of struct or union is taken, it may result in an unaligned pointer value. This patch adds -Waddress-of-packed-member to check alignment at pointer assignment and warn unaligned address as well as unaligned pointer

我刚刚从源代码构建了那个版本的 gcc.对于上述程序,它会产生以下诊断信息:

I've just built that version of gcc from source. For the above program, it produces these diagnostics:

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;
      |               ^~~~~~~~~

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

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