在C语言中,“packed”结构是什么?

46

我正在阅读一些为Microchip C30编译器编写的C代码,我经常看到如下定义的结构体:

typedef struct __attribute__((__packed__)) 
{
    IP_ADDR     MyIPAddr;               // IP address
    IP_ADDR     MyMask;                 // Subnet mask
    IP_ADDR     MyGateway;              // Default Gateway
        // etc...
} APP_CONFIG;

packed是什么意思?

7个回答

102
当定义结构时,编译器允许添加填充(没有实际数据的空格),以使成员落在更易于CPU访问的地址边界上。
例如,在32位CPU上,32位成员应该从4字节的倍数地址开始,以便有效地访问(读取和写入)。以下结构定义在两个成员之间添加了16位填充,以使第二个成员落在正确的地址边界上:
struct S {
    int16_t member1;
    int32_t member2;
};

在32位架构中,上述结构在内存中的结构为(~表示填充):

+---------+---------+
| m1 |~~~~|   m2    |
+---------+---------+

当一个结构体被打包时,这些填充字符不会被插入。编译器必须生成更多的代码(运行更慢),以提取非对齐的数据成员,并向它们写入数据。

当结构体被打包时,它将在内存中呈现为类似以下的形式:

+---------+---------+
| m1 |   m2    |~~~~
+---------+---------+

2
离题一点,但为什么会创建一个紧凑的结构体,如果它会使事情变慢?是为了减少内存占用吗? - damned
8
一个使用案例:它们经常用于定义和实现网络协议、二进制格式等。通常情况下,您不希望为将要被存储或发送到网络上的结构添加填充。 - jjmontes
考虑您的例子,16位 + 32位 = 48位 = 12字节,这确实是4字节的倍数。那么为什么编译器会应用填充呢? - Jumpman
2
@Maxitj,48位是6个字节,而不是12个。 - Juliano
@Juliano,我真傻,当我阅读这篇文章时非常疲惫 :) - Jumpman
对于一些处理器(如x86),非对齐访问不需要额外的目标代码。但是由于处理器的微码执行了额外的操作,执行速度仍然可能较慢。 - undefined

8

这将指示编译器在struct的成员之间不添加任何填充。

例如,可以参考此页面


我猜你的意思是相反的- packed 意味着省略任何填充而不是添加它。 - flolo
这个答案中的链接已经失效了,有修复的可能吗? - Kev
1
“不在成员之间添加任何填充”更像是添加最小的填充。某些架构仍可能在某些情况下强制执行一些填充。(例如,int必须在偶地址边界上以防止总线故障。) - chux - Reinstate Monica

7

让我通过一个例子来解释结构体中的填充(padding)概念,接着介绍紧凑型结构体(packed structures)。

然后我们再看看为什么需要进行紧凑化。

填充:

struct eg_struct
{
           unsigned char abc;
           unsigned int  xyz;
}

如果在16位架构上声明结构如上所述,则变量abc将被分配某个地址。下一个地址不会分配给变量xyz,而是添加了一个额外的字节,然后下一个地址才会分配给变量xyz

最终,该结构看起来像下面这样:

struct eg_struct
{
           unsigned char abc;
           unsigned char paddedbytes[1];
           unsigned int  xyz;
}

填充(Padding)可以使成员变量的地址易于访问微控制器,但缺点是会出现多余的字节。

打包:

如果使用“packed”属性声明相同的结构,则在变量abc后不会添加额外的字节。

以下是需要使用打包的一个例子:

考虑一个与EEPROM相接口的微控制器,其中存储了某个结构。

假设一个写入EEPROM的函数如下:

Write_EEPROM(EEPROM address, Ram address, Byte count);

现在如果不进行打包,多余的填充字节会占用EEPROM中的空间,这对其没有任何作用。

@laurenz albe 感谢你让我的回答更加易读。 - Babajan

2

有一件事情还没有明确指出,那就是打包通常是为了匹配预定义的字段结构。例如,在网络接口的低层层次上,网络机器之间交换一系列字节。数据接收后,需要将其映射到高级结构,以便轻松操作数据。这时通常需要无填充,以便结构直接映射到字节。

网络数据交换还涉及字节序问题(即几乎所有网络数据都使用大端格式,而不考虑源和目标计算机的字节序)。

此外,一些机器不能在非对齐地址中访问宽数据,例如,Cortex-M0核心不能在非32位对齐地址中访问32位数据,因此在这种情况下编写网络代码时必须小心。


1

当在结构声明期间使用打包时,编译器不会向相同结构的成员添加任何填充。以下是示例代码和输出,其自我说明。

$ cat structure_packed.c
#include <stdio.h>

typedef struct __attribute__((__packed__))
{
        char a;
        int ai;
        char ac;
}A;

struct B
{
        char b;
        int bi;
        char bc;
};

int main()
{
         A a;
        struct B b;
        int c;
        printf("size of struct A: %lu, addr a: %p, addr ai: %p, addr ac: %p\n", sizeof(a), &(a.a), &(a.ai), &a.ac);
        printf("size of struct B: %lu, addr b: %p, addr bi: %p, addr bc: %p\n", sizeof(b), &(b.b), &(b.bi), &b.bc);
        printf("addr of c: %p\n", &c);
        return 0;
}

编译

$ gcc structure_packed.c -o structure_packed

运行|输出

$ ./structure_packed
size of struct A: 6, addr a: 0x7ffc6f177ed6, addr ai: 0x7ffc6f177ed7, addr ac: 0x7ffc6f177edb
size of struct B: 12, addr b: 0x7ffc6f177edc, addr bi: 0x7ffc6f177ee0, addr bc: 0x7ffc6f177ee4
addr of c: 0x7ffc6f177ed0

1

_attribute__((__packed__)) 的意思(很可能)是“不要插入任何填充以加快速度”,也可能意味着“不要插入任何对齐方式以保留对齐方式”。


0
为什么会有人创建紧凑结构,尽管它会使事情变慢?
因为除了减少内存占用之外,当结构从最大到最小的成员手动紧凑排列时,通常所有的内存访问都会在偶对齐的内存地址上进行,这在某些微处理器上显著提高了性能。
一些微处理器无法在奇数内存地址上访问大于一个字节的内存,这会导致陷阱并导致代码崩溃。
一些微处理器在访问大于一个字节的内存时会遭受严重的惩罚,需要更多的代码,使缓存失效,或者两者都需要。
请参阅Eric S. Raymond的优秀著作《结构紧凑的失传艺术》。

http://www.catb.org/esr/structure-packing/


这并没有回答问题。一旦你有足够的声望,你就可以评论任何帖子;相反,提供不需要提问者澄清的答案。- 来自审查 - undefined
我很想知道我的回答为什么没有回答到“如果使用压缩结构会使事情变慢,为什么还要创建压缩结构?”这个问题。谢谢。 - undefined
这似乎是对damned在Juliano的回答上的评论的回复,与问题只有间接相关。 - undefined
是的,那是正确的。 - undefined
你的回答可以通过提供更多的支持性信息来改进。请编辑以添加进一步的细节,例如引用或文档,以便他人可以确认你的回答是否正确。你可以在帮助中心找到有关如何撰写好回答的更多信息。 - undefined
显示剩余3条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接