gcc 的 __attribute__((packed))/#pragma pack 不安全吗? [英] Is gcc's __attribute__((packed)) / #pragma pack unsafe?
问题描述
在 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].x
或arr[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屋!