1. 程式人生 > >C語言的位元組對齊及#pragma pack的使用

C語言的位元組對齊及#pragma pack的使用

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.