內容提要windows
l 字節對齊概念網絡
l 字節對齊測試數據結構
n offsetof測試
n 缺省狀況的字節對齊優化
n double 型字節對齊ui
n 改變字節對齊設置編碼
l 不一樣環境下的字節對齊spa
n GCC字節對齊操作系統
n ADS 字節對齊指針
l 字節對齊練習
字節對齊是一個很隱含的概念,平時可能你沒有留意,可是若是你在編寫網絡通信程序或者用結構去操做文件或硬件通信結構,這個問題就會浮出水面。我記得第一次致使我去看字節對齊概念資料的緣由就是ARP通信,ARP包頭是一個31Byte包頭。當你用一個認爲是31Byte結構去處理數據包時,卻老是處理不對。這一篇文章詳細討論了字節對齊要領和各類狀況.
字節對齊概念
l 現代計算機中內存空間都是按照byte劃分的,從理論上講彷佛對任何類型的變量的訪問能夠從任何地址開始,但爲了CPU訪問數據的快速,一般都要求數據存放的地址是有必定規律的.
l 好比在32位CPU上,通常要求變量地址都是基於4位,這樣能夠保證CPU用一次的讀寫週期就能夠讀取變量.不按4位對齊,若是變量恰好跨4位編碼,這樣須要CPU兩個讀寫週期.效率天然低下.所以,在現代的編譯器都會自動把複合數據定義按4位對齊,以保證CPU以最快速度讀取,這就是字節對齊(byte Alignment)產生的背景
l 字節對齊是一種典型,以空間換時間的策略的,在現代計算機擁有較大的內存的狀況,這個策略是至關成功的.
爲何要字節對齊?
l 加快程序訪問速度
l 不少CPU對訪問地址有嚴格要求,這時編譯器必需要這個CPU的規範來實現,X86較爲寬鬆,不對齊結構可能隻影響效率,如ARM,訪問地址必須基於偶地址,MIPS和Sparc也相似,這樣不對齊的地址訪問會形成錯誤.
關於字節對齊的實現
在不一樣的CPU對地址對齊有不一樣要求,各個編譯器也會採用不一樣策略來實現字節對齊,在隨後的例子,能夠對比PC下的Windows,和Linux,以有ARM下的字節對齊策略.
字節對齊帶來的問題
字節對齊至關於編譯器自已在開發者定義的結構裏偷偷加入一些填充字符,並且各類編譯器填充的策略不必定相同.所以,在網絡傳輸,二進制文件處理以及.底層總線傳輸和底層數據等相關領域,忽略字節對齊會帶來嚴重問題.這樣會產生錯位使用程序處理數據徹底錯誤.所以,網絡以及硬件相關開發人員必須對字節對齊要有清晰的瞭解.
字節對齊測試
offsetof 操做符
在分析字節對齊以前,首先了解一下offsetof宏.這個宏是標準C的定義,每一個C庫均會在stddef.h中定義.做用是計算結構或聯合每個成員的偏移量.用offsetof咱們能夠很清晰看到字節是如何對齊的.
它的用法以下:
typedef struct { char c1; int i1; char c2; } S3;
printf(「c1 offset=%d,i1 offset =%d,c2 offset=%d/n」,
offsetof(S3,c1),offsetof(S3,i1),offsetof(S3,c2));
offsetof在不一樣操做系統下定成不一樣形式.
/* Keil 8051 */
#define offsetof(s,m) (size_t)&(((s *)0)->m)
/* Microsoft x86 */
#ifdef _WIN64
#define offsetof(s,m) (size_t)( (ptrdiff_t)&( ( (s *)0 )->m ) )
#else
#define offsetof(s,m) (size_t)&( ( (s *) 0 )->m )
#endif
/* Motorola coldfire */
#define offsetof(s,memb) ((size_t)((char *)&((s *)0)->memb-(char *)0))
/* GNU GCC 4.0.2 */
#define offsetof(TYPE, MEMBER) __builtin_offsetof (TYPE, MEMBER)
注意:offsetof 不能求位域成員的偏移量,offsetof 雖然引用了一個空指針來操做成員,可是因爲只是在取類型,而且這個值在編譯期就被肯定,因此編譯器在編譯會直接算出offsetof的值,而不會在運行期引發內存段錯誤.
如下咱們用offsetof來分析結構和字節對齊
缺省狀況的字節對齊
缺省的狀況咱們是指32Bit CPU,Windows 使用VC++ 6.0,用這個環境基本能說明問題,其他的環境有不一樣的,再補充說明.
對齊有以下狀況:
1. 基本類型變量起始地址要按必定規則對齊.
l char 類型,其起始地址要1字節邊界上,即其地址能被1整除(即任意地址便可)
l short類型,其起始地址要2字節邊界上,即其地址能被2整除
l int 類型,其起始地址要4字節邊界上,即其地址能被4整除
l long類型,其起始地址要4字節邊界上,即其地址能被4整除
l float類型,其起始地址要4字節邊界上,即其地址能被4整除
l double類型,其起始地址要8字節邊界上,即其地址能被8整除
2. 結構實例起始址要在本身最大尺寸成員的對齊地址上
如最大尺寸的成員是short,則要基於2對齊
3. 結構內成員的偏移量也要參照第1條,知足相應倍數
如成員是short,則偏移量也是2的倍數.
這一條實際仍然是第1條規則的擴展,由於結構起始地址按最大倍數來,加上內部相應倍數,這樣成員絕對地址仍然知足第1條規定
4. 結構總尺寸也要對齊. 要爲最大尺寸的成員的整數倍,
若是不是則要在結構最後補齊成整數倍
關於第一條,咱們作以下測試
{ char c1; int i1; short o1; double d1; #define ADDR_DIFF(a,b) ((char*)a) - ((char *)b)
printf("c1 addr=0x%x,i1 addr=0x%x,o1 addr=0x%x,d1 addr=0x%x/n", &c1,&i1,&o1,&d1); printf("c1-i1 =%d,i1-o1=%d,o1-d1=%d/n", ADDR_DIFF(&c1,&i1),ADDR_DIFF(&i1,&o1),ADDR_DIFF(&o1,&d1)); } |
Win32下測試結果:
c1 addr=0x12ff7c, i1 addr=0x12ff78,o1 addr=0x12ff74,d1 addr=0x12ff6c
c1-i1 =4,i1-o1=4,o1-d1=8
從測試結果能夠看出,編譯器並無緊密的把各個數據結構排列在一塊兒,而是按其對齊地址進行分配
結構的字節對齊
例1:
typedef struct s2{
int a;
short b;
char c;
}s2;
printf("s2 size=%d,int a=%d,short b=%d,char c=%d/n",
sizeof(s2),offsetof(s2,a),offsetof(s2,b),offsetof(s2,c));
測試結果是 s2 size=8,int a=0,short b=4,char c=6
從結果看.是總尺寸是8,各成員尺寸之和是7,從偏移量能夠看在最後補齊一個字符,這是按規則4,總尺寸是最大成員倍數
例2:
typedef struct s5{
int a;
char b;
short c;
}s5;
printf("s5 size=%d,int a=%d,char b=%d,short c=%d/n",
sizeof(s5),offsetof(s5,a),offsetof(s5,b),offsetof(s5,c));
測試結果是 s5 size=8,int a=0,char b=4,short c=6
這一次補齊的目的是爲了short 型的c基於2對齊,應用第3條規則
例3:
typedef struct s10{
char b;
int a;
short c;
}s10;
printf("s10 size=%d,char b=%d,int a=%d,short c=%d/n",
sizeof(s10),offsetof(s10,b),offsetof(s10,a),offsetof(s10,c));
測試結果: s10 size=12,char b=0,int a=4,short c=8
第一次補齊的目的是爲了int 型的a基於4對齊,應用第3條規則
第二次補齊爲了合符第4條規則.要爲int的倍數.
例5:
typedef struct s4{
char a;
short b;
char c;
}s4;
printf("s4 size=%d,int a=%d,short b=%d,char c=%d/n",
sizeof(s4),offsetof(s4,a),offsetof(s4,b),offsetof(s4,c));
測試結果: s4 size=6,int a=0,short b=2,char c=4
這裏最大尺寸的成員是short b因此總尺寸是2的倍數,並且short自己也須要2對齊,所以在兩個不一樣地方補了一個byte
double型的字節對齊
先看測試樣例
typedef struct s1{
char a;
double b;
short c;
}s1;
printf("s1 size=%d,char a=%d,double b=%d,short c=%d/n",
sizeof(s1),offsetof(s1,a),offsetof(s1,b),offsetof(s1,c));
在Windows +VC 6.0下測試結果: s1 size=24,char a=0,double b=8,short c=16
在Redhat 9.0 +gcc 3.2.2下測試結果: s1 size=16,char a=0,double b=4,short c=12
能夠看到在兩個編譯器上,對double的對齊處理不同.在Linux下,double 採用是基於4對齊.而Windows採用8對齊.
再看一個實例
typedef struct s1{
char a;
double b;
char c;
int d;
}s1;
printf("s6 size=%d,char a=%d,double b=%d,char c=%d int d=%d/n",
sizeof(s6),offsetof(s6,a),offsetof(s6,b),offsetof(s6,c),offsetof(s6,d));
在Windows +VC 6.0下測試結果: s6 size=24,char a=0,double b=8,char c=16 int d=20
在Redhat 9.0 +gcc 3.2.2下測試結果: s6 size=20,char a=0,double b=4,char c=12 int d=16
改變字節對齊設置
默認的字節對齊都是按最大成員尺寸來進行對齊,可是在開發中可能須要調整對齊寬度.最常的一種狀況是,在在網絡和底層傳輸中取消字節對齊,完成按原始尺寸緊密的排列.
還有一種狀況是擴大或縮少字節對齊的排列.這種狀況比較複雜.但應用比較少.
取消字節對齊
在文件處理,網絡和底層傳輸中,數據都是緊密排列.不但願編譯器在結構內部自行增長空間.這時須要開發者通知編譯器,某一些結構是不須要字節對齊的.
絕大部分編譯器是使用預編譯指令pragma取消對齊
l #pragma pack (n) 設置對齊寬度爲n,它能夠是1,2,4,8等等,其中1就表示不進行字節對齊.
n # pragma pack (n)是成片生效的,即在這個指令後面全部結構都會按新的對齊值進行對齊
l # pragma pack() 將上一次# pragma pack (n)的設置取消.恢復爲默認值.
l 二者是成對使用,在這二者之間全部結構均受到影響
注意是pragma,不是progma
例子:
#pragma pack(1)
typedef struct s7{
int a;
short b;
char c;
}s7;
#pragma pack()
printf("s7 size=%d,int a=%d,short b=%d,char c=%d/n",
sizeof(s7),offsetof(s7,a),offsetof(s7,b),offsetof(s7,c));
測試結果 s7 size=7,int a=0,short b=4,char c=6
能夠看到,取消字節對齊,sizeof()就成員尺寸之和.
改變字節對齊
這種狀況比較複雜,並且也不經常使用.也是經過#pragma pack(n)來完成生效,可是要注意,
字節對齊值採用n和默認對齊值中較小的一個.換句話說,擴大對齊值是不生效的.
#pragma pack還有其它功能
l #pragma pack(push) // 將當前pack設置壓棧保存
l #pragma pack(pop) // 恢復先前的pack設置
這兩個功能用於多種對齊值混用的場合,(固然,這種狀況也是很是少見)
縮小例子:
#pragma pack (2) /*指定按2字節對齊,缺省是4 */
typedef struct s8
{
char a;
int b;
short c;
}s8;
#pragma pack ()
printf("s8 size=%d,char a=%d,int b=%d,short c=%d/n",
sizeof(s8),offsetof(s8,a),offsetof(s8,b),offsetof(s8,c));
測試結果: s8 size=8,char a=0,int b=2,short c=6
缺省的4字節對齊話,sizoef應該是12,如今改成2對齊的話,只能是8,即在char a 補了一個字節.
擴大的例子:
#pragma pack (8) /*指定按2字節對齊,缺省是4 */
typedef struct s9
{
char a;
int b;
short c;
}s9;
#pragma pack ()
printf("s9 size=%d,char a=%d,int b=%d,short c=%d/n",
sizeof(s9),offsetof(s9,a),offsetof(s9,b),offsetof(s9,c));
測試結果:s9 size=12,char a=0,int b=4,short c=8
這個結果跟4對齊是同樣的,換句話說,8對齊沒有生效
不一樣環境下的字節對齊使用
GCC的字節對齊控制
GCC也支持#pragma 字節控制
l #pragma pack (n),gcc將按照n個字節對齊
l #pragma pack (),取消自定義字節對齊方式
#pragma 只保證的成員相關偏移量是字節對齊的.不保證絕對地址對齊.
GCC也支持某個一個數據結構實現絕對地址的天然對齊
__attribute((aligned (n))) 讓所做用的結構成員對齊在n字節天然邊界上。若是結構中有成員的長度大於n,則按照最大成員的長度來對齊。
__attribute__ ((packed)),取消結構在編譯過程當中的優化對齊,按照實際佔用字節數進行對齊。
struct STRUCT_TEST
{
char a;
int b;
char c;
} __attribute__ ((packed)); //注意位置,在}與;之間
l __attribute是GCC屬性,跟#pragma 不一樣, __attribute__是gcc的方言,只有GCC能識別,不要在VC++之類編譯器使用這種定義.
l __attribute每次只對一個結構生效.
ADS的字節對齊控制
ARM對訪問地址有特殊要求,若是不對齊,會形成程序錯誤,而不是象X86或PowerPC那樣折成兩個指令訪問. 所以用#pragma pack(1) 只是讓結構自己成員內部按1對齊,並不能保證結構的絕對地址是對齊.
ADS採用特殊指令來實現要想保證地址對齊.ADS採用ALIGN.__align(num), .__packed,來控制字節對齊
l ALIGN 用於彙編的字節對齊控制
l __align(num) 相似於#pragma pack(num),用於整片代碼字節對齊的的控制.
l __packed 取消某個結構或成員的內部字節對齊,並實現絕對地址對齊,相似於gcc的__attribute__ ((packed));
__packed struct STRUCT_TEST
{
char a;
int b;
char c;
} ;
字節對齊練習
請指在windows 32下出下列值的sizeof和各個成員的偏移量
1. struct s1{
short a;
short b;
short c;
};
2. struct s2{
char a[21];
short b;
};
3. struct s2{
float a;
char b;
short c;
int d;
};
5. #pragma pack (1)
typedef struct s8
{
char a;
int b;
short c;
double d;
}s8;
#pragma pack ()