简体   繁体   English

gcc的__attribute __((packed))/ #pragma pack是否不安全?

[英]Is gcc's __attribute__((packed)) / #pragma pack unsafe?

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. 在C语言中,编译器将按声明的顺序对结构的成员进行布局,并在成员之间或最后一个成员之后插入填充字节,以确保每个成员正确对齐。

gcc provides a language extension, __attribute__((packed)) , which tells the compiler not to insert padding, allowing struct members to be misaligned. gcc提供了语言扩展__attribute__((packed)) ,它告诉编译器不要插入填充,从而使结构成员无法对齐。 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. 例如,如果系统通常要求所有int对象具有4字节对齐,则__attribute__((packed))可以使int struct成员分配为奇数偏移量。

Quoting the gcc documentation: 引用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. “ packed”属性指定变量或结构字段应具有尽可能小的对齐方式-变量一个字节,一个字段一位,除非您使用“ aligned”属性指定更大的值。

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. 是的, __attribute__((packed))在某些系统上可能不安全。 The symptom probably won't show up on an x86, which just makes the problem more insidious; 该症状可能不会出现在x86上,这只会使问题更加隐蔽。 testing on x86 systems won't reveal the problem. 在x86系统上进行测试不会发现问题。 (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.) (在x86上,未对齐的访问是在硬件中处理的;如果取消引用指向奇数地址的int*指针,则它会比正确对齐的速度慢一点,但会得到正确的结果。)

On some other systems, such as SPARC, attempting to access a misaligned int object causes a bus error, crashing the program. 在某些其他系统上,例如SPARC,尝试访问未对齐的int对象会导致总线错误,从而使程序崩溃。

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: 在具有gcc 4.5.2的x86 Ubuntu上,它将产生以下输出:

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: 在带有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

In both cases, the program is compiled with no extra options, just gcc packed.c -o packed . 在这两种情况下,程序都无需额外的选项即可gcc packed.c -o packed ,仅需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.) (使用单个结构而不是数组的程序无法可靠地显示该问题,因为编译器可以将结构分配给奇数地址,因此x成员正确对齐。对于两个struct foo对象的数组,至少一个否则另一个将具有未对齐的x成员。)

(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.) (在这种情况下, p0指向未对齐的地址,因为它指向在char成员之后的压缩int成员p1恰好正确对齐,因为它指向数组第二个元素中的同一成员,因此存在它前面有两个char对象-在SPARC Solaris上,数组arr似乎分配给的地址是偶数,但不是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. 当通过名称引用struct foo的成员x时,编译器会知道x可能未对齐,并将生成其他代码以正确访问它。

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. 一旦将arr[0].xarr[1].x的地址存储在指针对象中,编译器和运行程序都不会知道它指向未对齐的int对象。 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. 我认为,在gcc中修复此问题不切实际。 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. 对于每次尝试将指针解除对具有非平凡对齐要求的类型的尝试,通用解决方案都需要(a)在编译时证明指针没有指向打包结构的未对齐成员,或者(b)生成可以处理对齐或未对齐对象的笨重且较慢的代码。

I've submitted a gcc bug report . 我已经提交了gcc错误报告 As I said, I don't believe it's practical to fix it, but the documentation should mention it (it currently doesn't). 正如我所说,我认为修复它不切实际,但是文档中应该提到它(目前还没有)。

UPDATE : As of 2018-12-20, this bug is marked as FIXED. 更新 :从2018年12月20日开始,此错误被标记为已修复。 The patch will appear in gcc 9 with the addition of a new -Waddress-of-packed-member option, enabled by default. 该修补程序将出现在gcc 9中,并添加了新的-Waddress-of-packed-member选项,默认情况下已启用。

When address of packed member of struct or union is taken, it may result in an unaligned pointer value. 当采用struct或union的打包成员的地址时,可能会导致指针值未对齐。 This patch adds -Waddress-of-packed-member to check alignment at pointer assignment and warn unaligned address as well as unaligned pointer 此修补程序添加了-Waddress-of-packed-member以检查指针分配时的对齐方式,并警告未对齐的地址以及未对齐的指针

I've just built that version of gcc from source. 我刚刚从源代码构建了该版本的gcc。 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;
      |               ^~~~~~~~~

As ams said above, don't take a pointer to a member of a struct that's packed. 如上文所述,请勿使用指向已压缩结构的成员的指针。 This is simply playing with fire. 这简直是​​在玩火。 When you say __attribute__((__packed__)) or #pragma pack(1) , what you're really saying is "Hey gcc, I really know what I'm doing." 当您说__attribute__((__packed__))#pragma pack(1) ,您真正要说的是“嘿,gcc,我真的知道我在做什么。” When it turns out that you do not, you can't rightly blame the compiler. 事实证明您没有这样做,就不能正确地责怪编译器。

Perhaps we can blame the compiler for it's complacency though. 也许我们可以责怪编译器的自满。 While gcc does have a -Wcast-align option, it isn't enabled by default nor with -Wall or -Wextra . 尽管gcc确实具有-Wcast-align选项,但默认情况下也未启用-Wall-Wextra This is apparently due to gcc developers considering this type of code to be a brain-dead " abomination " unworthy of addressing -- understandable disdain, but it doesn't help when an inexperienced programmer bumbles into it. 显然,这是由于gcc开发人员认为这种类型的代码是不值得解决的脑死的“ 可恶 ”,这是可以理解的轻蔑,但是当没有经验的程序员陷入其中时,这无济于事。

Consider the following: 考虑以下:

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;

Here, the type of a is a packed struct (as defined above). 这里,类型a是填充结构(如上所定义)。 Similarly, b is a pointer to a packed struct. 同样, b是指向压缩结构的指针。 The type of of the expression ai is (basically) an int l-value with 1 byte alignment. 表达式ai的类型(基本上)是一个1字节对齐的int l值 c and d are both normal int s. cd都是正常的int When reading ai , the compiler generates code for unaligned access. 读取ai ,编译器会生成未对齐访问的代码。 When you read b->i , b 's type still knows it's packed, so no problem their either. 当您阅读b->ib的类型仍然知道它是压缩的,因此它们也没问题。 e is a pointer to a one-byte-aligned int, so the compiler knows how to dereference that correctly as well. e是指向1字节对齐的int的指针,因此编译器也知道如何正确地对其取消引用。 But when you make the assignment f = &a.i , you are storing the value of an unaligned int pointer in an aligned int pointer variable -- that's where you went wrong. 但是,当您使赋值f = &a.i ,您f = &a.i未对齐的int指针的值存储在对齐的int指针变量中-这就是您出错的地方。 And I agree, gcc should have this warning enabled by default (not even in -Wall or -Wextra ). 我同意,gcc应该默认情况下启用此警告(即使在-Wall-Wextra也不应该启用)。

It's perfectly safe as long as you always access the values through the struct via the . 只要您始终通过struct通过结构访问值,这是绝对安全的. (dot) or -> notation. (点)或->表示法。

What's not safe is taking the pointer of unaligned data and then accessing it without taking that into account. 什么不安全的是采取非对齐数据的指针,然后访问它没有考虑到这一点。

Also, even though each item in the struct is known to be unaligned, it's known to be unaligned in a particular way , so the struct as a whole must be aligned as the compiler expects or there'll be trouble (on some platforms, or in future if a new way is invented to optimise unaligned accesses). 而且,即使已知结构中的每个项目都是未对齐的,但也知道它是以特定方式未对齐的,因此,整个结构必须按照编译器的预期进行对齐,否则会出现麻烦(在某些平台上,或者将来,如果发明了一种优化未对齐访问的新方法)。

Using this attribute is definitely unsafe. 使用此属性绝对是不安全的。

One particular thing it breaks is the ability of a union which contains two or more structs to write one member and read another if the structs have a common initial sequence of members. 它破坏的一件事是包含两个或多个结构的union的能力,如果一个结构具有相同的成员初始序列,则该结构可以写入一个成员并读取另一个成员。 Section 6.5.2.3 of the C11 standard states: C11标准的 6.5.2.3节规定:

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. 6为了简化并集的使用,做出了一项特殊保证:如果并集包含多个具有相同初始序列的结构(请参见下文),并且如果并集对象当前包含这些结构之一,则可以检查该并集。可以看到联合的完成类型的声明的任何位置的公共部分。 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: 9示例3以下是有效的片段:

 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 (unalltypes == 1) if (sin(u.nf.doublenode) == 0.0) /* ... */ 

When __attribute__((packed)) is introduced it breaks this. 当引入__attribute__((packed)) ,它打破了这一点。 The following example was run on Ubuntu 16.04 x64 using gcc 5.4.0 with optimizations disabled: 以下示例使用gcc 5.4.0在Ubuntu 16.04 x64上运行,并且禁用了优化功能:

#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\n", sizeof(struct s1), sizeof(struct s2));
    printf("s.y.a=%hx, s.y.b=%x\n", s.y.a, s.y.b);
    return 0;
}

Output: 输出:

sizeof s1 = 6, sizeof s2 = 8
s.y.a=1234, s.y.b=5678

Even though struct s1 and struct s2 have a "common initial sequence", the packing applied to the former means that the corresponding members don't live at the same byte offset. 即使struct s1struct s2具有“公共初始序列”,应用于前者的打包也意味着相应的成员不在相同的字节偏移量处生存。 The result is the value written to member xb is not the same as the value read from member yb , even though the standard says they should be the same. 结果是写入成员xb的值与从成员yb读取的值不同,即使标准规定它们应该相同。

(The following is a very artificial example cooked up to illustrate.) One major use of packed structs is where you have a stream of data (say 256 bytes) to which you wish to supply meaning. (下面是一个非常人为的示例,用来说明问题。)打包结构的主要用途之一是,您希望提供数据流(例如256个字节)。 If I take a smaller example, suppose I have a program running on my Arduino which sends via serial a packet of 16 bytes which have the following meaning: 如果我举一个较小的例子,假设我的Arduino上运行了一个程序,该程序通过串行发送一个16字节的数据包,其含义如下:

0: message type (1 byte)
1: target address, MSB
2: target address, LSB
3: data (chars)
...
F: checksum (1 byte)

Then I can declare something like 然后我可以声明类似

typedef struct {
  uint8_t msgType;
  uint16_t targetAddr; // may have to bswap
  uint8_t data[12];
  uint8_t checksum;
} __attribute__((packed)) myStruct;

and then I can refer to the targetAddr bytes via aStruct.targetAddr rather than fiddling with pointer arithmetic. 然后我可以通过aStruct.targetAddr来引用targetAddr字节,而不必摆弄指针算法。

Now with alignment stuff happening, taking a void* pointer in memory to the received data and casting it to a myStruct* will not work unless the compiler treats the struct as packed (that is, it stores data in the order specified and uses exactly 16 bytes for this example). 现在发生对齐问题, 除非在内存中将void *指针指向接收到的数据并将其强制转换为myStruct *, 否则编译器将其视为打包后的(即,它以指定的顺序存储数据并恰好使用16字节)。 There are performance penalties for unaligned reads, so using packed structs for data your program is actively working with is not necessarily a good idea. 对于未对齐的读取会产生性能损失,因此使用打包的结构存储程序正在积极使用的数据不一定是一个好主意。 But when your program is supplied with a list of bytes, packed structs make it easier to write programs which access the contents. 但是,当为程序提供字节列表时,打包的结构使编写访问内容的程序更容易。

Otherwise you end up using C++ and writing a class with accessor methods and stuff that does pointer arithmetic behind the scenes. 否则,您最终将使用C ++并使用访问器方法和在后台执行指针算术的东西编写一个类。 In short, packed structs are for dealing efficiently with packed data, and packed data may be what your program is given to work with. 简而言之,打包结构用于有效处理打包数据,打包数据可能是您的程序可以使用的。 For the most part, you code should read values out of the structure, work with them, and write them back when done. 在大多数情况下,您的代码应从结构中读取值,使用它们,并在完成后将其写回。 All else should be done outside the packed structure. 其他所有操作都应在打包结构之外进行。 Part of the problem is the low level stuff that C tries to hide from the programmer, and the hoop jumping that is needed if such things really do matter to the programmer. 问题的一部分是C试图向程序员隐藏的低级内容,以及如果这样的事情确实对程序员很重要,则需要跳环。 (You almost need a different 'data layout' construct in the language so that you can say 'this thing is 48 bytes long, foo refers to the data 13 bytes in, and should be interpreted thus'; and a separate structured data construct, where you say 'I want a structure containing two ints, called alice and bob, and a float called carol, and I don't care how you implement it' -- in C both these use cases are shoehorned into the struct construct.) (您几乎需要用该语言编写一个不同的“数据布局”结构,以便您可以说“此内容长48个字节,foo指的是其中13个字节的数据,因此应对此进行解释”;以及一个单独的结构化数据结构,在这里您说“我想要一个包含两个int的结构,分别称为alice和bob和一个称为carol的浮点数,并且我不在乎您如何实现它” –在C中,这两个用例都被塞入struct结构中。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 GCC 上的 #pragma pack(push, n)/#pragma pack(pop) 和 __attribute__((__packed__,aligned(n))) 之间有什么区别? - What are the differences between #pragma pack(push, n)/#pragma pack(pop) and __attribute__((__packed__, aligned(n) )) on GCC? __attribute __(packed)v / s GCC __attribute __((aligned(x)) - __attribute__(packed) v/s GCC __attribute__((aligned(x)) Pragma Pack Ignored和__attribute __((aligned(2),packed)); 没有任何影响 - Pragma Pack Ignored and __attribute__ ((aligned (2), packed)); does not have any effect Visual C ++相当于GCC的__attribute __((__ package ___)) - Visual C++ equivalent of GCC's __attribute__ ((__packed__)) GCC的__attribute __((__ packed__))是否保留原始排序? - Does GCC's __attribute__((__packed__)) retain the original ordering? 像__attribute __((packed))这样的属性,并非特定于GCC - A __attribute__((packed)) like attribute not GCC Specific 我是否误解了GCC中的__attribute __((包装))? - Am I misunderstanding __attribute__ ((packed)) in GCC? 是 __attribute__((packed)) GCC 还是跨平台的? - Is __attribute__((packed)) GCC only, or cross platform? GCC:__attribute__和#pragma的对齐设置 - GCC: Alignment settings with __attribute__ and #pragma 结构使用GNU GCC中的__attribute __((__ packed__))打包 - Structure Packing using __attribute__((__packed__)) in GNU GCC
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM