C语言的字节对齐及#pragma pack的使用

原文:https://www.cnblogs.com/dabiao/archive/2010/04/15/1712458.html

C编译器的缺省字节对齐方式(自然对界)


在缺省情况下,C编译器为每一个变量或是数据单元按其自然对界条件分配空间。

在结构中,编译器为结构的每个成员按其自然对界(alignment)条件分配空间。各个成员按照它们被声明的顺序在内存中顺序存储(成员之间可能有插入的空字节),第一个成员的地址和整个结构的地址相同。

 

C编译器缺省的结构成员自然对界条件为“N字节对齐”,N即该成员数据类型的长度。如int型成员的自然对界条件为4字节对齐,而double类型的结构成员的自然对界条件为8字节对齐。若该成员的起始偏移不位于该成员的“默认自然对界条件”上,则在前一个节面后面添加适当个数的空字节。

 

C编译器缺省的结构整体的自然对界条件为:该结构所有成员中要求的最大自然对界条件。若结构体各成员长度之和不为“结构整体自然对界条件的整数倍,则在最后一个成员后填充空字节。

以下例子中假设实际数据为1,为了对齐进行的填充为0.


例子1(分析结构各成员的默认字节对界条界条件和结构整体的默认字节对界条件):

struct Test
{ 
char x1; // 成员x1为char型(其起始地址必须1字节对界),其偏移地址为0 

char x2; // 成员x2为char型(其起始地址必须1字节对界,其偏移地址为1 

float x3; // 成员x3为float型(其起始地址必须4字节对界),编译器在x2和x3之间填充了两个空字节,其偏移地址为4 

char x4; // 成员x4为char型(其起始地址必须1字节对界),其偏移地址为8 
};
因为Test结构体中,最大的成员为float x3,因些此结构体的自然对界条件为4字节对齐。则结构体长度就为12字节,内存布局为1100 1111 1000。

例子2:

#include <stdio.h>

typedef struct
{
  int aa1; //4个字节对齐 1111
  char bb1;//1个字节对齐 1
  short cc1;//2个字节对齐 011
  char dd1; //1个字节对齐 1
  } testlength1;
int length1 = sizeof(testlength1); //4个字节对齐,占用字节1111 1011 1000,length = 12

typedef struct
{
  char bb2;//1个字节对齐 1
  int aa2; //4个字节对齐 0001111
  short cc2;//2个字节对齐 11
  char dd2; //1个字节对齐 10
  } testlength2;
int length2 = sizeof(testlength2); //4个字节对齐,占用字节1000 1111 1110,length = 12


typedef struct
{
  char bb3; //1个字节对齐 1
  char dd3; //1个字节对齐 1
  int aa3; //4个字节对齐 001111
  short cc23//2个字节对齐 11

  } testlength3;
int length3 = sizeof(testlength3); //4个字节对齐,占用字节1100 1111 1100,length = 12


typedef struct
{
  char bb4; //1个字节对齐 1
  char dd4; //1个字节对齐 1
  short cc4;//2个字节对齐 11
  int aa4; //4个字节对齐 1111
  } testlength4;
int length4 = sizeof(testlength4); //4个字节对齐,占用字节1111 1111,length = 8


int main(void)
{
  printf("length1 = %d.\n",length1);
  printf("length2 = %d.\n",length2);
  printf("length3 = %d.\n",length3);
  printf("length4 = %d.\n",length4);
  return 0;
}

改变缺省的对界条件(指定对界)
· 使用伪指令#pragma pack (n),C编译器将按照n个字节对齐。· 使用伪指令#pragma pack (),取消自定义字节对齐方式。

这时,对齐规则为:

1、数据成员对齐规则:结构(struct)(或联合(union))的数据成员,第一个数据成员放在offset为0的地方,以后每个数据成员的对齐按照#pragma pack指定的数值和这个数据成员自身长度中,比较小的那个进行。

2、结构(或联合)的整体对齐规则:在数据成员完成各自对齐之后,结构(或联合)本身也要进行对齐,对齐将按照#pragma pack指定的数值和结构(或联合)最大数据成员长度中,比较小的那个进行。

结合1、2推断:当#pragma pack的n值等于或超过所有数据成员长度的时候,这个n值的大小将不产生任何效果。

 

因此,当使用伪指令#pragma pack (2)时,Test结构体的大小为8,内存布局为11 11 11 10。

需要注意一点,当结构体中包含一个子结构体时,子结构中的成员按照#pragma pack指定的数值和子结构最大数据成员长度中,比较小的那个进行进行对齐。例子如下:

#pragma pack(8)
struct s1{
short a;
long b;
};


struct s2{
char c;
s1 d;
long long e;
};
#pragma pack()


sizeof(s2)的结果为24。S1的内存布局为1100 1111,S2的内存布局为1000 1100 1111 0000 1111 1111。


例子:

#include <stdio.h>
#pragma pack(2)
typedef struct
{
  int aa1; //2个字节对齐 1111
  char bb1;//1个字节对齐 1
  short cc1;//2个字节对齐 011
  char dd1; //1个字节对齐 1
  } testlength1;
int length1 = sizeof(testlength1); //2个字节对齐,占用字节11 11 10 11 10,length = 10

typedef struct
{
  char bb2;//1个字节对齐 1
  int aa2; //2个字节对齐 01111
  short cc2;//2个字节对齐 11
  char dd2; //1个字节对齐 1
  } testlength2;
int length2 = sizeof(testlength2); //2个字节对齐,占用字节10 11 11 11 10,length = 10


typedef struct
{
  char bb3; //1个字节对齐 1
  char dd3; //1个字节对齐 1
  int aa3; //2个字节对齐 11 11
  short cc23//2个字节对齐 11

  } testlength3;
int length3 = sizeof(testlength3); //2个字节对齐,占用字节11 11 11 11,length = 8


typedef struct
{
  char bb4; //1个字节对齐 1
  char dd4; //1个字节对齐 1
  short cc4;//2个字节对齐 11
  int aa4; //2个字节对齐 11 11
  } testlength4;
int length4 = sizeof(testlength4); //2个字节对齐,占用字节11 11 11 11,length = 8


int main(void)
{
  printf("length1 = %d.\n",length1);
  printf("length2 = %d.\n",length2);
  printf("length3 = %d.\n",length3);
  printf("length4 = %d.\n",length4);
  return 0;
}

另外,还可以采用#pragma pack(push,n) 或者#pragma pack(push)  与 #pragma pack(pop)来保存/恢复当前的对齐值。

  1. #pragma pack(n) simply sets the new alignment.
  2. #pragma pack() sets the alignment to the one that was in effect when compilation started.
  3. #pragma pack(push[,n]) pushes the current alignment setting on an internal stack and then optionally sets the new alignment.
  4. #pragma pack(pop) restores the alignment setting to the one saved at the top of the internal stack (and removes that stack entry). Note that #pragma pack([n]) does not influence this internal stack; thus it is possible to have #pragma pack(push) followed by multiple #pragma pack(n) instances and finalized by a single #pragma pack(pop).
至于何时应该使用,以下是google上的一个反馈,整体意思是尽量不用,会导致移植性问题以及性能问题,除非是在网络协议传输等必须使用的场合。

In general you should not use #pragma pack. Yes, it will make your structures smaller in memory since it eliminates all padding between struct members. But it can makeaccessing those members much more expensive since the members may no longer fall along their required alignment. For example, in ARM architectures, 4-byte ints are typically required to be 4-byte aligned, but in a packed struct they might not be. That means the compiler needs to add extra instructions to safely access that struct member, or the developer has to access it byte-by-byte and reconstruct the int manually. Either way it results in more code than an aligned access, so your struct ends up smaller but your accessing code potentially ends up slower and larger.

You should use #pragma pack when your structure must match anexact data layout. This typically happens when you are writing code to match a data transport or access specification... e.g., network protocols, storage protocols, device drivers that access HW registers. In those cases you may need#pragma pack to force your structures to match the spec-defined data layout. This will possibly incur the same performance penalty mentioned in the previous paragraph, but may be the only way to comply with the specification.




阅读更多
想对作者说点什么?

博主推荐

换一批

没有更多推荐了,返回首页