关于结构的存储分配(__packed)问题

1570阅读 0评论2015-09-26 coolgw1106
分类:C/C++

   记得刚学c语言的时候对结构体(struct)和联合(union)两者区别的认识是:结构体的大小是其所有成员大小之和,而联合大小则是其最大成员所占字节的大小。今天作了个小小的测试发现这种认识不完全正确。
这是《c与指针》上的说明:
    编译器按照成员类别的顺序一个一个的给每个成员分配内存。只有当存储成员时需要满足正确的边界对齐要求时,成员之间才可能出现用于填充的额外内存空间。(硬件注意事项---边界对齐;在要求边界对齐的机器上,整形值的起始位置只能是某些特定字节,通常是2或者4的倍数);
关于边界对齐:(来自百度百科)
    各个硬件平台对存储空间的处理上有很大的不同。一些平台对某些特定类型的数据只能从某些特定地址开始存取。比如有些架构的CPU在访问 一个没有进行对齐的变量的时候会发生错误,那么在这种架构下编程必须保证字节对齐.其他平台可能没有这种情况,但是最常见的是如果不按照适合其平台要求对 数据存放进行对齐,会在存取效率上带来损失。比如有些平台每次读都是从偶地址开始,如果一个int型(假设为32位系统)如果存放在偶地址开始的地方,那么一个读周期就可以读出这32bit,而如果存放在奇地址开始的地方,就需要2个读周期,并对两次读出的结果的高低字节进行拼凑才能得到该32bit数据。[显然这是在时间和空间之间做出的一种取舍](字节对齐的实现和具体的编译器有关)

点击(此处)折叠或打开

  1. struct S{
  2. char a;
  3. int b;
  4. char c;
  5. };

如果某个机器的整型值长度是4个字节,并且它的起始存储位置必须能够被四整除,那么这一结构在内存中的存储为:
白色方块表示结构成员的存储位置。
    系统禁止编译器在一个结构的起始位置跳过几个字节来满足边界对齐的要求,因此所有结构的起始存储位置必须是结构中边界要求最严格的数据类型所要求的位置。因此,a成员必须存储于一个能别4整除的地址。结构的下一个成员时一个整型值,所有它必须跳过3个字节(灰色表示)到适合的边界才能存储。在整形之后是最后一个字符。
    如果声明了相同类型的第2个变量,它的起始存储位置也必须满足4这个边界,所有第一个结构的后面还要再跳过3个字节才能存储第二个结构。因此,每个结构将占据12个字节的存储空间但实际只是用其中的6个,这个利用率不是很出色。按照这种存储方法,结构的大小不是所有成员大小之后,而是成员数*最大成员所占内存大小
   当然可以再声明中对结构的结构成员类别进行重新排列,让那些对边界要求最严格的成员首先出现,对边界要求最弱的成员最后出现。这种做法可以最大限度的减少因边界对齐所带来的空间损失。
例如将上面的例子改写为:

点击(此处)折叠或打开

  1. struct S{
  2. int b;
  3. char a;
  4. char c;
  5. };
将边界要求为4的整型b最先出现,同样的结构体它只占用了8个字节,节省了33%的空间。两个字符可以紧凑这存储,所有只有结构最后面需要跳过两个字节才被浪费。
c语言提供了一种机制来解决这种在struct里填充字节来满足对齐要求。这种机制就是__packed(packed前面是两个下划线)。
__attribute__:是GNU C的一大特色机制。
__attribute__可以设置函数属性(Function Attribute)、变量属性(Variable Attribute)和类型属性(Type Attribute)
__attribute__前后都有两个下划线,并且后面会紧跟一对原括弧,括弧里面是相应的__attribute__参数
__attribute__语法格式为:
__attribute__ ( ( attribute-list ) )
函数属性(Function Attribute),函数属性可以帮助开发者把一些特性添加到函数声明中,从而可以使编译器在错误检查方面的功能更强大。
__attribute__机制也很容易同非GNU应用程序做到兼容。
关键字__attribute__可以对结构体(struct)或共用体(union)进行属性设置。
有六个参数可供选择:aligned,packed,transparent_union,unused,deprecated,may_alias
这里我们讨论packed使用该属性可以使得变量或者结构体成员使用最小的对齐方式,即对变量是一字节对齐,对域(field)是位对齐。
更多关于__attribute__的使用方法说明:
http://blog.csdn.net/sunboy_2050/article/details/6566739

下面程序说明一下:

点击(此处)折叠或打开

  1. #include <stdio.h>
  2. #define __packed __attribute__((packed))

  3. struct S1{
  4. double a;
  5. char b;
  6. char c;
  7. };

  8. struct S{
  9. char a;
  10. double b;
  11. char c;
  12. };

  13. struct __packed ps{
  14. char a;
  15. double b;
  16. char c;
  17. };
  18.  
  19. int main(void)
  20. {
  21. struct S1 test0;
  22. struct S test1;
  23. struct ps  test2;
  24. printf("no_packed=%d\n",sizeof(test0));
  25. printf("no_packed=%d\n",sizeof(test1));
  26. printf("packed=%d\n",sizeof(test2));
  27. printf("double=%d\n",sizeof(double));
  28. printf("char=%d\n",sizeof(char));
  29. return 0;
  30. }
结果:
no_packed=12  //通过调整成员位置节省空间
no_packed=16
packed=10
double=8
char=1
可以看出来__attribute__((packed))即:__packed可以使编译器不在struct中添加填充字节,这样可以最大限度的节省存储空间。在嵌入式开发中尤为重要。
上面程序使用的长整形占用的8个字节,其还是采用的被4整除的方法进行存储。明白这点之后我们就很容易分析出结果了。





上一篇:linux设备驱动归纳总结(十三):1.触摸屏与ADC时钟
下一篇:Linux中的内存管理