Is gcc's __attribute__((packed)) / #pragma pack unsafe?
C,编译出来的一个成员会躺在struct顺序在其中声明他们是可能的,与padding位元组插入构件之间,或在最后的成员,每个成员是aligned,确保正确地。
gcc提供语言扩展,
quoting的gcc文件:
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可以导致在较小的数据要求,但必须作为较慢的代码,编译代码(在一些platforms)的统一,也产生一个misaligned成员访问一个字节在一个时间。
但在任何情况下,这是unsafe哪里有?并不总是统一,也产生correct编译的代码(虽然较慢)成员的访问misaligned structs包装?它甚至有可能为它,所以在所有的情况下?
是的,是潜在的不安全
在其他一些制度,如SPARC,N的访问对象A misaligned
有一个系统的访问是在misaligned盘从低阶位的地址,错误的信息,访问内存块。
考虑下面的程序:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | #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; } |
在Ubuntu和GCC 4.5.2 x86,它包含以下输出:
1 2 3 4 5 6 7 8 9 |
在SPARC Solaris 9和GCC 4.5.1,副车架的下面的信息:
1 2 3 4 5 6 7 8 |
在这两种情况下,程序是在没有额外的选项,只是
这一程序(使用单一阵列结构还比不reliably文化问题,因为编译器可以分配在奇数地址的结构是在这样一个
(本案例中,A misaligned
当一个成员
一旦
这是固定在一个海湾,impractical相信。一般的解决方案是需要的,对于每个dereference指向任何类型的一个非平凡的对准要求。(A)试验,在编译时指针不到一个点misaligned of a填充结构,或(b)和生成的代码,可以将bulkier对准或是misaligned对象句柄。
我已经提交bug报告A gcc。当我说,我不相信它是实际的固定文档应该提到它,但它(它目前没有)。
更新:截至2018年12 20,这是固定的标记为错误。GCC的补丁要出现在9与一
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。在上面的程序,它包含的主要诊断:
1 2 3 4 5 6 7 | 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; | ^~~~~~~~~ |
如AMS上面所说,不要将指针指向打包的结构的成员。这只是玩火。当你说
也许我们可以把它的自满归咎于编译器。虽然GCC有一个
考虑以下内容:
1 2 3 4 5 6 7 8 9 10 11 | struct __attribute__((__packed__)) my_struct { char c; int i; }; struct my_struct a = {'a', 123}; struct my_struct *b = &a; int c = a.i; int d = b->i; int *e __attribute__((aligned(1))) = &a.i; int *f = &a.i; |
这里,
它是完全安全的只要你总是访问值,通过结构,以
什么是不安全的指针数据是以大学unaligned然后访问它,以是没有考虑到。
因此,即使是在每个项目的unaligned结构已知,它是已知的unaligned A的方式,因此,结构作为一个整体必须对准为编译器将expects或有故障(或在一些平台上,如果一个新的方式是创造未来。unaligned accesses优化)。
使用此属性绝对不安全。
它破坏的一个特殊的东西是
6 One special guarantee is made in order to simplify the use of unions:
if a union contains several structures that share a common
initial sequence (see below), and if the union object
currently contains one of these structures, it is permitted
to inspect the common initial part of any of them anywhere that a
declaration of the completed type of the union is visible. Tw o
structures share a common initial sequence if corresponding
members have compatible types (and, for bit-fields, the same widths)
for a sequence of one or more initial members....
9 EXAMPLE 3 The following is a valid fragment:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23 union {
struct {
int alltypes;
}n;
struct {
int type;
int intnode;
} ni;
struct {
int type;
double doublenode;
} nf;
}u;
u.nf.type = 1;
u.nf.doublenode = 3.14;
/*
...
*/
if (u.n.alltypes == 1)
if (sin(u.nf.doublenode) == 0.0)
/*
...
*/
当引入
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | #include <stdio.h> #include <stdlib.h> struct s1 { short a; int b; } __attribute__((packed)); struct s2 { short a; int b; }; union su { struct s1 x; struct s2 y; }; int main() { union su s; s.x.a = 0x1234; s.x.b = 0x56789abc; printf("sizeof s1 = %zu, sizeof s2 = %zu ", sizeof(struct s1), sizeof(struct s2)); printf("s.y.a=%hx, s.y.b=%x ", s.y.a, s.y.b); return 0; } |
输出:
1 2 | sizeof s1 = 6, sizeof s2 = 8 s.y.a=1234, s.y.b=5678 |
虽然
(以下是一个非常人为的例子,旨在说明这一点。)打包结构的一个主要用途是,您有一个数据流(例如256字节),希望为其提供含义。如果我举一个较小的例子,假设我的Arduino上运行着一个程序,它通过串行发送一个16字节的包,其含义如下:
1 2 3 4 5 6 | 0: message type (1 byte) 1: target address, MSB 2: target address, LSB 3: data (chars) ... F: checksum (1 byte) |
那么我可以申报类似的东西
1 2 3 4 5 6 | typedef struct { uint8_t msgType; uint16_t targetAddr; // may have to bswap uint8_t data[12]; uint8_t checksum; } __attribute__((packed)) myStruct; |
然后,我可以通过astruct.targetaddr引用targetaddr字节,而不是随意修改指针算法。
现在,随着对齐的事情的发生,在内存中取一个void*指针指向接收到的数据并将其强制转换为mystruct*将不起作用,除非编译器将该结构视为压缩的(即,它按指定的顺序存储数据,并且在本例中正好使用16个字节)。对于未对齐的读操作会有性能损失,因此对程序正在积极处理的数据使用打包结构不一定是个好主意。但是,当您的程序被提供一个字节列表时,压缩结构使编写访问内容的程序变得更容易。
否则,你最终会使用C++并用访问器方法编写一个类,在后台做指针运算。简而言之,打包的结构用于高效地处理打包数据,打包的数据可能是程序要处理的内容。在大多数情况下,代码应该从结构中读取值,使用它们,并在完成后将其写回。所有其他工作都应在包装结构之外进行。问题的一部分是C试图对程序员隐藏的低级内容,以及如果这些事情确实对程序员有影响,则需要进行的循环跳跃。(您在语言中几乎需要一个不同的"数据布局"构造,这样您就可以说"这个东西长48字节,foo指的是中的数据13字节,应该这样解释";以及一个单独的结构化数据构造,其中您说"我想要一个包含两个in t的结构,称为alice和bob,以及一个名为carol的float,我不在乎"如何实现它"——在C中,这两个用例都被shoehorned到struct结构中。)